關(guān)于MySQL事務(wù),你必須知道的幾個(gè)知識(shí)點(diǎn)!
Transaction事務(wù)
上期我們講到了jpa的常用操作,查詢(xún)、更新、刪除等,但是如果在操作數(shù)據(jù)庫(kù)事務(wù)時(shí)發(fā)生異常 ,數(shù)據(jù)會(huì)回滾嗎?下面我們來(lái)看個(gè)例子
UserController新增如下代碼:
- @GetMapping("save1")
- public String save1(){
- User user = new User();
- user.setDptId(1L);
- user.setName("a");
- user.setAge(18L);
- user.setEmail("a@a.com");
- user.setHeadImg("headImg1");
- this.userJpa.save(user);
- //模擬發(fā)生了異常
- System.out.println(1/0);
- return "ok";
- }
使用postman請(qǐng)求
- localhost:8080/user/save1
執(zhí)行之后可以看到j(luò)ava后臺(tái)報(bào)錯(cuò)了,postman前臺(tái)也報(bào)出來(lái)錯(cuò)誤,但是數(shù)據(jù)卻保存進(jìn)去了,數(shù)據(jù)新增了一條記錄
說(shuō)明即使發(fā)生了異常,數(shù)據(jù)還是會(huì)保存進(jìn)去數(shù)據(jù)庫(kù),那應(yīng)該怎么辦呢?試試在save1方法上加一個(gè)@Transactional的注解。
我們?cè)賵?zhí)行一次。發(fā)現(xiàn)錯(cuò)誤也報(bào)出來(lái)了,但是數(shù)據(jù)庫(kù)并沒(méi)有將新數(shù)據(jù)插入進(jìn)去,最新的還是上一次的id為7的記錄,那么 Transactional注解是干嘛的呢?
@Transactional是聲明式事務(wù)管理編程中使用的注解
- 該注解是添加在實(shí)現(xiàn)類(lèi)或者 接口 實(shí)現(xiàn)方法上,而不能放在 接口 中
- 需要注意的是這個(gè)注解只對(duì)public方法生效
如下是該注解的屬性,我們需要關(guān)注重點(diǎn)關(guān)注的是rollback-for和propagation兩個(gè)屬性。
屬性名 | 說(shuō)明 |
---|---|
name | 當(dāng)在配置文件中有多個(gè) TransactionManager , 可以用該屬性指定選擇哪個(gè)事務(wù)管理器。 |
propagation | 事務(wù)的傳播行為,默認(rèn)值為 REQUIRED。 |
isolation | 事務(wù)的隔離度,默認(rèn)值采用 DEFAULT。 |
timeout | 事務(wù)的超時(shí)時(shí)間,默認(rèn)值為-1。如果超過(guò)該時(shí)間限制但事務(wù)還沒(méi)有完成,則自動(dòng)回滾事務(wù)。 |
read-only | 指定事務(wù)是否為只讀事務(wù),默認(rèn)值為 false;為了忽略那些不需要事務(wù)的方法,比如讀取數(shù)據(jù),可以設(shè)置 read-only 為 true。 |
rollback-for | 用于指定能夠觸發(fā)事務(wù)回滾的異常類(lèi)型,如果有多個(gè)異常類(lèi)型需要指定,各類(lèi)型之間可以通過(guò)逗號(hào)分隔。 |
no-rollback- for | 拋出 no-rollback-for 指定的異常類(lèi)型,不回滾事務(wù)。 |
rollback-for:只有執(zhí)行的異常才回滾。但是我們剛剛的程序并沒(méi)有指定異常,那是默認(rèn)的是遇到什么樣的異常會(huì)回滾呢?
- 將UserController中的代碼稍作修改,手動(dòng)
throw new Exception("test")
,再執(zhí)行下postman,發(fā)現(xiàn)事務(wù)提交了,并沒(méi)有回滾。 - 接著我們將注解修改為
@Transactional(rollbackFor = Exception.class)
,再執(zhí)行postman,事務(wù)卻回滾了,并沒(méi)有提交,什么原因? - spring的
@Transactional
注解可以很方便的開(kāi)啟事務(wù),但是默認(rèn)只在遇到運(yùn)行時(shí)異常
和Error
時(shí)才會(huì)回滾,非運(yùn)行時(shí)異常不回滾,即Exception
的子類(lèi)中,除了RuntimeException
及其子類(lèi),其他的類(lèi)默認(rèn)不回滾。 - 而rollbackFor屬性可以解決這個(gè)問(wèn)題,
rollbackFor = Exception.class
表示Exception
及其子類(lèi)的異常都會(huì)觸發(fā)回滾,同時(shí)不影響Error
的回滾。
propagation:這個(gè)用得最廣的需求就是業(yè)務(wù)出錯(cuò)了,但是日志必須提交到數(shù)據(jù)庫(kù)。怎么處理?來(lái)看下面的代碼。
新增LogService類(lèi)
- @Service
- public class LogService {
- @Resource
- private UserJpa userJpa;
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void saveLog(){
- User user = new User();
- user.setDptId(1L);
- user.setName("log");
- user.setAge(18L);
- user.setEmail("log@log.com");
- user.setHeadImg("log");
- this.userJpa.save(user);
- System.out.println("log");
- }
- }
新增UserService類(lèi):
- @Service
- public class UserService {
- @Resource
- private UserJpa userJpa;
- @Resource
- private LogService logService;
- @Transactional(rollbackFor = Exception.class)
- public void saveBiz() throws Exception {
- System.out.println("save2");
- User user = new User();
- user.setDptId(1L);
- user.setName("biz");
- user.setAge(18L);
- user.setEmail("biz@biz.com");
- user.setHeadImg("biz");
- this.userJpa.save(user);
- //模擬保存日志
- this.logService.saveLog();
- //模擬發(fā)生了異常
- throw new Exception("test1");
- }
- }
UserController新增代碼
- @GetMapping("save2")
- public String save2() throws Exception {
- //模擬業(yè)務(wù)操作
- this.userService.saveBiz();
- return "ok";
- }
postman執(zhí)行下,是不是只有l(wèi)og的那條記錄插入進(jìn)去了?biz的沒(méi)有插入進(jìn)去。
注意:同一個(gè)業(yè)務(wù)類(lèi)里面 , 即使聲明為 Propagation.REQUIRES_NEW
也不會(huì)新啟一個(gè)事務(wù)。必須調(diào)用另一個(gè)類(lèi)的 Propagation.REQUIRES_NEW
方法才行。所以樣例中是使用 UserService
里面調(diào)用另一個(gè)類(lèi) LogService
中的 saveLog
的方法。