Spring事務傳播屬性介紹(一).required 和 reuqires_new


Mandatory、Never、Not_Support傳播屬性分析傳送門:https://www.cnblogs.com/lvbinbin2yujie/p/10260030.html

  Nested傳播屬性分析傳送門:https://www.cnblogs.com/lvbinbin2yujie/p/10260066.html

  我的Spring事務傳播屬性介紹比較傳送門:https://files.cnblogs.com/files/lvbinbin2yujie/Spring_Tx_Note.rar

 

 

最近查看了Spring事務源碼,是4.2.x的版本還是4.3.x的版本,簡單了解了一些事務的概念,介紹下我對Spring事務源碼的分析.

Spring一共七種事務傳播屬性,本文先來作為開篇介紹。

  REQUIRED事務,Spring Transactional注解默認的事務,需要該方法在有事務情況下運行,如果當前沒有事務就新建一個事物;

  REQUIRES_NEW事務,當前方法運行沒有事務,新建一個事物,當前方法有事務將當前事務掛起,新事物執行完畢再恢復原有事務;

這里提一點.,以前不明白什么是同一事務核心是什么,后來看到有位仁兄介紹,同一事務的事務信息、事務狀態對象不同,但是底層是同一個Connection對象;這點我深以為.

 

實驗說明環節

包結構:          

 

Spring配置文件:  (簡單介紹下, 定義了一個數據源、DataSourceTransactionManager;此外tx:annotation-driven作用是用來啟用Transactional注解的)

     

ServiceA.java文件

 

ServiceB.java文件

 

測試Main方法:

 

 

PROPAGATION_REQUIRED

說明: 默認的事務級別, 需要事務;如果當前沒有事務,則創建新的事務;如果有事務呢,就加入當前的事務;

如果所有的Transactional標簽都是默認的,REQUIRED時,方法里的提交、回滾都是一起的,要么所有都提交,要么所有都回滾;一榮俱榮,一損俱損;

 

查看源碼時候打印事務的日志:

 

查看輸出結果:  可以看到 確實提交了,並且加入了之前的事務,加入之前事務就是共用的一個Connection對象;

 

 情景二:  修改ServiceB的addUser方法  來模擬調用其他業務方法時候執行拋出運行時異常;  (同時將數據庫之前測試結果 刪除,便於觀察)

 

   先查看結果:  艾尼路記錄添加進來又回退了, 查看日志:

 

 

說明:  addUser拋出類型為RuntimeException類型的異常,callB捕獲該異常之后,發現addUser在事物callB中,於是將事務狀態DefaultTransactionStatus對戲中的事務對象DataSourceTransactionObject中的ConnectionHolder對象,即持有底層Connection的對象,將ConnectionHolder標記為rollbackOnly為true,然后將異常拋給調用addUser的callB方法;

在callB調用時根據Spring事務回滾規則,決定回滾操作;因為在情景二同一個事務中ConnectionHolder中持有的Connection對象是同一個,所以callB方法整個回滾了;

 

 

情景三: callB方法中手動try-catch來捕獲異常會發生什么呢?

         修改ServiceA的callB方法(為了便於觀察,不輸出異常,只是簡單打印)

 

直接查看輸出日志(最后一行拋出的異常排版原因未截全): 查看日志,記錄確實添加了但是又回滾過了,數據庫里沒有記錄。

異常信息:

Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

 

 

說明: callB作為事務的開始,addUser作為事務的一部分,addUser拋出異常以后將ConnectionHolder對象的rollbackOnly設置為true,標記為需要回滾,但是執行完callB方法,沒有拋出異常,就認為應該正常提交; 提交之前會校驗兩次,有一次是 Global transaction is marked as rollback-only but transactional code requested commit;

意思是全局事務需要回滾操作,但是事務代碼現在要提交,這時候Spring還是會回滾;(通俗點就是addUser方法需要回滾,但是callB方法沒有發生預料之外的異常,因為異常自己手動捕獲,這時候Spring還是會回滾);

         callB和ServiceB的事務信息對象TransactionInfo是不一樣的,其屬性事務狀態TransactionStatus也不是同一個對象,TransactionStatus的底層事務對象transaction也不是同一個對象,transaction持有ConnectionHolder對象的Connection確實同一個,這樣就保證了可以使用Connection對象來保持事務的一致性,一起提交、一起回滾; 保證兩個Connection是同一個對象的是ThreadLocal,TransactionSynchronizationManager的resources里存放了着;

 

總結:  REQUIRED不建議手動捕獲異常,會破壞Spring的事務規則;try-catch需要結合傳播類型,再決定使用與否;

 

PROPAGATION_REQUIRES_NEW

說明: 創建新的事務並執行,如果當前有事務,那將當前事務掛起,新建一個事物;

 

給ServiceA類callB方法修改下:

ServiceB類添加方法addUserFail,事務屬性設置為REQUIRE_NEW

 

正常情況下查看輸出日志:

可以發現,當進入REQUIRED_NEW事務里的方法時,掛起了原來的事務,事務執行完畢恢復了事務;並且外層事務和addUserFail事務是分別提交的;

 

 

情景三.

ServiceA方法:

 

ServiceB方法:

測試類方法:

 

 

說明:callB2方法調用了ServiceB的兩個事務方法,ddUserSuccess方法是REQUIRED事務,addUserFail方法是REQUIRED_NEW事務,按照之前分析的,REQUIRED_NEW的方法是一個新的事務,那我拋出異常自己就會回滾,不應該干擾到callB2方法的回滾;

執行結果: addUserSuccess方法被帶着一起回滾了,即外層事務也被帶着一起回滾了;

 

查看源碼發現: addUserFail的確新建了事務,然后拋出異常之后,着手回滾,回滾完成后將異常throw了,異常被throw那就會丟給調用addUserFail的地方,沒錯,丟到了callB2方法里,那callB2也着火了,發生異常,這時候callB2的事務也被認定為執行失敗應當回滾,那callB2的事務就開始回滾,callB2內部事務addUserSuccess回滾, 所以一條記錄都沒有寫進去.

(想了下,REQUIRED_NEW事務的方法就在調用他的地方手動捕獲異常,不讓異常向上傳遞了,這樣就能達到目的,且不會像REQUIRED一樣報異常)

 

簡單畫了如下例子,外層事務為REQUIRED類型;外層事務有兩個方法,A、B;Spring默認回滾規則為RuntimeException或Error類型,下面例子拋出異常也是RumtimeException或Error類型,且沒有手動try-catch捕獲異常;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM