一個99%的人都說不清楚知識點——Spring 事務傳播行為
面試過很多人,大部分都能把事務的四個特性及隔離級別說得七七八八,但當問到 Spring 的傳播行為時,就基本上沒人能說出個一二三了。
我們都知道,一個事務要么成功,要么失敗。但當若干個事務配合完成一個復雜任務時,就不能簡單的這樣一刀切了。我們需要根據(jù)任務之間的親疏關(guān)系來指定哪些任務需要聯(lián)動回滾,哪些任務即使失敗也不會影響其他任務。要解決這個問題,就需要了解事務的傳播行為了。Spring 中有七種事務的傳播行為,如下表所示:

Spring 可以通過 @Transactional 注解的 propagation 屬性來設(shè)置不同的傳播行為策略。Spring 為此提供了一個枚舉類 Propagation,源碼如下:
接下來我們通過對其中三種最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略進行對比來更深入的理解。以下測試均在外部方法開啟事務的情況下進行,因為在外部沒有事務的情況下,三者都會新建事務,效果一樣。
REQUIRED
當內(nèi)部方法的事務傳播行為設(shè)置為 REQUIRED 時,內(nèi)部方法會加入外部方法的事務。我們在 UserServiceImpl 中添加如下方法:
創(chuàng)建 TransactionServiceImpl 類,并添加如下方法:
結(jié)果分析如下表所示:

前面四種情況都比較好理解,很多人不能理解最后一種情況:我都 try-catch 了你還想怎樣?這里的關(guān)鍵點在于所有方法都處于同一個事務中,此時「小鏡」的插入方法發(fā)生異常,那么這個方法所在的事務就會被 Spring 設(shè)置為 rollback 狀態(tài)。因為異常被 catch 了,所以外部方法執(zhí)行完要進行 commit 操作,這時卻發(fā)現(xiàn)當前事務已經(jīng)處于 rollback 狀態(tài)了,雖然它不知道哪里出了問題,但也只能聽從指揮,回滾所有操作了。
PS:由于外部方法不開啟事務的情況,在每種傳播行為下結(jié)果都是類似的,所以后面不再給出示例。
REQUIRES_NEW
當內(nèi)部方法的傳播行為設(shè)置為 REQUIRES_NEW 時,內(nèi)部方法會先將外部方法的事務掛起,然后開啟一個新的事務 。在 UserServiceImpl 中添加如下方法:
在 TransactionServiceImpl 中添加如下方法:
結(jié)果分析如下表所示:

NESTED
當內(nèi)部方法的傳播行為設(shè)置為 NESTED 時,內(nèi)部方法會開啟一個新的嵌套事務(子事務)。在 UserServiceImpl 中添加如下方法:
在 TransactionServiceImpl 中添加如下方法:
結(jié)果分析如下表所示:

每個 NESTED 事務執(zhí)行前會將當前操作保存下來,叫做 savepoint (保存點),如果當前 NESTED 事務執(zhí)行失敗,則回滾到之前的保存點,保存點使得子事務的回滾不對主事務造成影響。NESTED 事務在外部事務提交以后自己才會提交。
總結(jié)
REQUIRES_NEW 最為簡單,不管當前有無事務,它都會開啟一個全新事務,既不影響外部事務,也不會影響其他內(nèi)部事務,真正的井水不犯河水,堅定而獨立。
REQUIRED 在沒有外部事務的情況下,會開啟一個獨立的新事務,且不會對其他同級事務造成影響;而當存在外部事務的情況下,則會與外部事務同生共死。
NESTED 在沒有外部事務的情況下與 REQUIRED 效果相同;而當存在外部事務的情況下,當外部事務回滾時,它會創(chuàng)建一個嵌套事務(子事務)。外部事務回滾時,子事務會跟著回滾;但子事務的回滾不會對外部事務和其他同級事務造成影響。