處理事務回滾
參考文獻:《極客時間-Java業務開發常見錯誤100例》https://time.geekbang.org/column/article/213295
大多數Spring Boot項目只需要在方法上標記@Transactional注解,即可一鍵開啟方法的事務性配置。
保證事務生效
-
務必確認調用 @Transactional 注解標記的方法是 public 的
除非特殊配置(比如使用 AspectJ 靜態織入實現 AOP),否則只有定義在 public 方法上的 @Transactional 才能生效。原因是,Spring 默認通過動態代理的方式實現 AOP,對目標方法進行增強,private 方法無法代理到,Spring 自然也無法動態增強事務處理邏輯。
如果要針對 private 方法啟用事務,動態代理方式的 AOP 不可行,需要使用靜態織入方式的 AOP,也就是在編譯期間織入事務增強代碼,可以配置 Spring 框架使用 AspectJ 來實現 AOP。你能否參閱 Spring 的文檔“Using @Transactional with AspectJ”試試呢?注意:AspectJ 配合 lombok 使用,還可能會踩一些坑。 https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-aspectj
-
通過 Spring 注入的 Bean 進行調用的。使用 try…catch…來包裹標記了 @Transactional 注解的方法,必須通過代理過的類從外部調用目標方法才能生效。
Spring 通過 AOP 技術對方法進行增強,要調用增強過的方法必然是調用代理后的對象。 - CGLIB 通過繼承方式實現代理類,private 方法在子類不可見,自然也就無法進行事務增強; - this 指針代表對象自己,Spring 不可能注入 this,所以通過 this 訪問方法必然不是代理。
正確使用:在 Service 內部注入自己調用自己的 方法() 可以正確實現事務,但更合理的實現方式是,讓 Controller 直接調用之前定義的 Service 的 方法() ,因為注入自己調用自己很奇怪,也不符合分層實現的規范
保證回滾
默認情況下,出現 RuntimeException(非受檢異常)或 Error 的時候,Spring 才會回滾事務。
修復方式:
-
自己捕捉,手動設置讓當前事務處於回滾狀態
@Transactionalpublic void createUserRight1(String name) { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed", ex); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
-
在注解中聲明
rollbackFor
,期望遇到所有的 Exception 都回滾事務(來突破默認不回滾受檢異常的限制)@Transactional(rollbackFor = Exception.class)
子方法回滾,主方法不回滾
雖然捕獲了子方法的異常,但是因為沒有開啟新事務,而當前事務因為異常已經被標記為rollback了,所以最終還是會回滾。
解決方法:想辦法讓子邏輯在獨立事務中運行
-
為注解加上
propagation = Propagation.REQUIRES_NEW
來設置 REQUIRES_NEW 方式的事務傳播策略,也就是執行到這個方法時需要開啟新的事務,並掛起當前事務。@Transactional(propagation = Propagation.REQUIRES_NEW)
主方法沒什么變化,同樣需要try catch捕獲異常,防止異常漏出去導致主事務回滾