使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

項目開發中不知道大家碰沒碰見過這種情況。在方法1中調用方法2(2個方法都是有事物的)。方法2出現異常要回滾,但是方法1不能回滾,並且要記錄方法2拋出的異常到數據庫中。看一個例子:

使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

例子1

使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

例子1結果

從代碼中可以看出,我都加上了Transactional,但是沒有一個事物生效的。2個方法的事物都沒起作用。先要知道Transaction的底層。

註解@Transaction的底層實現是Spring AOP技術,而Spring AOP技術使用的是動態代理。這就意味著對於靜態(static)方法和非public方法,註解@Transactional是失效的。還有一個更為隱秘的,而且在使用過程中極其容易犯錯誤的——自調用(也就是我現在代碼中出現的問題,不明白動態代理的可以上網百度下)。

使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

例子2

修改為上圖所示,事物都好使了,但是不符合我的需求。方法1回滾了。如何才能不讓方法1回滾。這裡就要從事物的傳播行為說起了。

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。如下幾個表示傳播行為的常量:

  • PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
  • PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
  • PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於PROPAGATION_REQUIRED。

由於Springboot中默認是PROPAGATION_REQUIRED,也就說insertData2會使用insertData的事物,這樣當insertData2拋出異常,被insertData的事物捕獲,就會認為是insertData整個方法異常了,就會事物回滾,也就造成了2個方法都沒有插入進去數據。

所以這裡需要將insertData2的事物傳播行為修改下,讓他不去使用insertData的事物,而是自己創建一個。

使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

insertData2方法

使用@Transactional遇見的問題,子方法回滾如何讓父方法不回滾

再次執行結果

insertData2回滾,沒有插入id為101的數據,insertData正常執行。那麼這裡是否有人會質疑我在事物中使用了try catch呢。正常在事物中都是建議直接拋出的,不然事物會捕獲不到異常的。但是在insertData方法中,如果不捕獲insertData2的異常,就會直接拋出一個java.lang.ArithmeticException: / by zero異常。insertData方法的事物是會捕獲到這個異常的,就會認為insertData出現錯誤了(而事實是insertData2出現錯誤),就會回滾insertData數據。就會造成insertData插入不進去數據。

總結:

  1. 對於靜態(static)方法和非public方法,自調用,註解@Transactional是失效的。
  2. spring的事務邊界是在調用業務方法之前開始的,業務方法執行完畢之後來執行commit or rollback(spring默認取決於是否拋出runtime異常),如果拋出runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。
  3. 一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作後一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產生髒數據。


分享到:


相關文章: