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