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捕獲異常;

