深入理解SpringBoot事務(wù)傳播機(jī)制
在 Spring Boot 開發(fā)中,事務(wù)是一個(gè)至關(guān)重要的概念,尤其是在涉及多層業(yè)務(wù)邏輯或者多個(gè)數(shù)據(jù)庫操作時(shí)。Spring 提供了強(qiáng)大的事務(wù)管理功能,使得開發(fā)者可以方便地控制事務(wù)的行為。事務(wù)傳播機(jī)制作為 Spring 事務(wù)管理的一部分,是 Spring 事務(wù)管理中一個(gè)非常重要的概念。
本文將介紹 Spring Boot 中事務(wù)傳播機(jī)制的原理及其常用配置,以幫助開發(fā)者更好地理解事務(wù)傳播的工作方式。
一、什么是事務(wù)傳播機(jī)制?
事務(wù)傳播機(jī)制定義了在多個(gè)方法中調(diào)用事務(wù)時(shí),事務(wù)的行為是如何傳播的。換句話說,它決定了一個(gè)事務(wù)方法在被另一個(gè)方法調(diào)用時(shí)應(yīng)該如何處理事務(wù)的開啟、提交、回滾等操作。
事務(wù)傳播機(jī)制通過 @Transactional
注解的 propagation
屬性來配置,它有多個(gè)傳播行為,開發(fā)者可以根據(jù)具體的需求來選擇合適的傳播方式。常見的傳播行為包括:
REQUIRED
REQUIRES_NEW
SUPPORTS
MANDATORY
NOT_SUPPORTED
NEVER
NESTED
二、Spring 事務(wù)傳播機(jī)制的傳播行為
1. REQUIRED(默認(rèn)傳播行為)
傳播行為: 如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù);如果當(dāng)前已經(jīng)存在事務(wù),則加入到現(xiàn)有事務(wù)中。
應(yīng)用場景: 這是最常用的傳播行為,通常在業(yè)務(wù)方法調(diào)用中使用,確保調(diào)用方法的一致性。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { // 業(yè)務(wù)邏輯 } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { // 業(yè)務(wù)邏輯 }
- 如果
methodA()
沒有事務(wù),它會新建一個(gè)事務(wù); - 如果
methodA()
已經(jīng)在一個(gè)事務(wù)中,那么methodB()
會加入到這個(gè)事務(wù)中。
2. REQUIRES_NEW
傳播行為: 總是新建一個(gè)事務(wù)。如果當(dāng)前有事務(wù)存在,則將當(dāng)前事務(wù)掛起,等新事務(wù)提交或回滾后再恢復(fù)當(dāng)前事務(wù)。
應(yīng)用場景: 當(dāng)我們希望某個(gè)方法獨(dú)立于當(dāng)前事務(wù)進(jìn)行處理,通常用于一些不希望受到外部事務(wù)影響的操作,例如日志記錄、通知等。
@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodC() { // 業(yè)務(wù)邏輯 }
- 無論
methodC()
調(diào)用時(shí)是否有事務(wù),它都會開啟一個(gè)新的事務(wù); - 如果
methodC()
調(diào)用時(shí)已經(jīng)有事務(wù)存在,它會將當(dāng)前事務(wù)掛起,開啟一個(gè)新的事務(wù); - 當(dāng)
methodC()
的事務(wù)結(jié)束后,原本掛起的事務(wù)會恢復(fù)繼續(xù)執(zhí)行。
3. SUPPORTS
傳播行為: 如果當(dāng)前有事務(wù),則加入到現(xiàn)有事務(wù)中;如果當(dāng)前沒有事務(wù),則以非事務(wù)方式執(zhí)行。
應(yīng)用場景: 當(dāng)方法支持事務(wù),但不強(qiáng)制要求事務(wù)存在時(shí),可以使用 SUPPORTS
。例如,一些方法可能不需要事務(wù),但如果存在事務(wù),它們會加入其中。
@Transactional(propagation = Propagation.SUPPORTS) public void methodD() { // 業(yè)務(wù)邏輯 }
- 如果
methodD()
調(diào)用時(shí)已有事務(wù),它將加入該事務(wù); - 如果沒有事務(wù),
methodD()
以非事務(wù)方式執(zhí)行。
4. MANDATORY
傳播行為: 如果當(dāng)前有事務(wù),則加入到現(xiàn)有事務(wù)中;如果沒有事務(wù),則拋出異常。
應(yīng)用場景: 如果方法依賴事務(wù)執(zhí)行,但又不希望自行創(chuàng)建事務(wù),則可以使用 MANDATORY
。如果沒有現(xiàn)有事務(wù),將拋出 TransactionRequiredException
異常。
@Transactional(propagation = Propagation.MANDATORY) public void methodE() { // 業(yè)務(wù)邏輯 }
- 如果當(dāng)前沒有事務(wù),
methodE()
會拋出異常; - 如果當(dāng)前有事務(wù),
methodE()
會加入到該事務(wù)中。
5. NOT_SUPPORTED
傳播行為: 如果當(dāng)前有事務(wù),則將當(dāng)前事務(wù)掛起,并以非事務(wù)方式執(zhí)行方法。
應(yīng)用場景: 當(dāng)某個(gè)方法不希望參與事務(wù)操作時(shí),可以使用 NOT_SUPPORTED
,例如一些查詢操作,它們無需事務(wù)支持。
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodF() { // 業(yè)務(wù)邏輯 }
- 如果當(dāng)前有事務(wù),
methodF()
會掛起當(dāng)前事務(wù),執(zhí)行時(shí)不支持事務(wù); - 如果沒有事務(wù),
methodF()
以非事務(wù)方式執(zhí)行。
6. NEVER
傳播行為: 如果當(dāng)前有事務(wù),則拋出異常;如果沒有事務(wù),則以非事務(wù)方式執(zhí)行。
應(yīng)用場景: 當(dāng)一個(gè)方法不允許在事務(wù)中運(yùn)行時(shí)使用。例如,一些特定的檢查方法,它們要求事務(wù)完全不存在。
@Transactional(propagation = Propagation.NEVER) public void methodG() { // 業(yè)務(wù)邏輯 }
- 如果當(dāng)前有事務(wù),
methodG()
會拋出IllegalTransactionStateException
異常; - 如果沒有事務(wù),
methodG()
以非事務(wù)方式執(zhí)行。
7. NESTED
傳播行為: 如果當(dāng)前沒有事務(wù),則新建一個(gè)事務(wù);如果當(dāng)前已有事務(wù),則在當(dāng)前事務(wù)中嵌套一個(gè)事務(wù)。嵌套事務(wù)可以獨(dú)立提交或回滾。
應(yīng)用場景: 如果你希望事務(wù)能夠嵌套,并且在嵌套事務(wù)回滾時(shí)不會影響外部事務(wù)的提交,可以使用 NESTED
。
@Transactional(propagation = Propagation.NESTED) public void methodH() { // 業(yè)務(wù)邏輯 }
- 如果
methodH()
內(nèi)部拋出異常并回滾,則不會影響外部事務(wù); - 如果
methodH()
成功提交,外部事務(wù)也會提交。
三、事務(wù)傳播機(jī)制原理分析
1. 事務(wù)的傳播原理
Spring 的事務(wù)傳播機(jī)制實(shí)際上是通過 AOP(面向切面編程)來實(shí)現(xiàn)的。Spring 在運(yùn)行時(shí)會生成一個(gè)代理對象(通常是 JDK 動態(tài)代理或 CGLIB 代理),在事務(wù)方法執(zhí)行時(shí),代理會負(fù)責(zé)判斷事務(wù)的傳播行為并根據(jù)行為決定是否開啟新的事務(wù)或加入到現(xiàn)有事務(wù)中。
- 事務(wù)開始:當(dāng)方法執(zhí)行時(shí),代理會檢查是否已有事務(wù)存在。如果沒有,則會根據(jù)傳播行為決定是否需要?jiǎng)?chuàng)建新的事務(wù)。
- 事務(wù)嵌套:對于
REQUIRES_NEW
或NESTED
傳播行為,Spring 會創(chuàng)建新的事務(wù),這些事務(wù)與外部事務(wù)相互獨(dú)立。 - 事務(wù)回滾:如果方法發(fā)生異常且指定了回滾規(guī)則,則代理會回滾事務(wù)。
- 事務(wù)提交:當(dāng)方法執(zhí)行成功,Spring 會提交事務(wù)。
2. 事務(wù)傳播機(jī)制的執(zhí)行順序
假設(shè)方法 A 調(diào)用方法 B,方法 B 使用 REQUIRES_NEW
傳播行為:
- 方法 A 開始執(zhí)行時(shí),判斷是否有事務(wù),如果沒有事務(wù),則開啟事務(wù)。
- 方法 A 調(diào)用方法 B,方法 B 會暫停方法 A 的事務(wù),并開啟自己的事務(wù)。
- 方法 B 執(zhí)行完成后,提交自己的事務(wù),并恢復(fù)方法 A 的事務(wù)。
這就是事務(wù)傳播機(jī)制在嵌套調(diào)用中的行為。
在 Spring 中,事務(wù)傳播機(jī)制的實(shí)現(xiàn)依賴于 AOP(面向切面編程),而 AOP 只會應(yīng)用于通過 Spring 管理的 bean。如果我們直接調(diào)用同一個(gè)類中的方法(即同一個(gè)實(shí)例的方法),則事務(wù)傳播機(jī)制可能會失效,因?yàn)?Spring 的代理對象并未被應(yīng)用到這些內(nèi)部方法調(diào)用中。以下是關(guān)于事務(wù)傳播機(jī)制的一些代碼示例,并且會展示事務(wù)傳播機(jī)制失效的場景。
四、代碼測試示例
1. REQUIRED 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 開始事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事務(wù)"); } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { System.out.println("methodB: 開始事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodB: 完成事務(wù)"); } public void testRequiredPropagation() { methodA(); // 這會觸發(fā)事務(wù)的傳播機(jī)制 methodB(); // 也會加入到當(dāng)前事務(wù)中 } }
預(yù)期輸出:
methodA: 開始事務(wù)
methodA: 完成事務(wù)
methodB: 開始事務(wù)
methodB: 完成事務(wù)
2. REQUIRES_NEW 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodC() { System.out.println("methodC: 開始新事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodC: 完成新事務(wù)"); } public void testRequiresNewPropagation() { methodC(); // 新事務(wù)會獨(dú)立執(zhí)行 } }
預(yù)期輸出:
methodC: 開始新事務(wù)
methodC: 完成新事務(wù)
3. SUPPORTS 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.SUPPORTS) public void methodD() { System.out.println("methodD: 支持事務(wù)(如果有)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodD: 完成"); } public void testSupportsPropagation() { methodD(); // 如果存在事務(wù),方法會加入到當(dāng)前事務(wù)中 } }
預(yù)期輸出:
如果沒有事務(wù):
methodD: 支持事務(wù)(如果有)
methodD: 完成
如果有事務(wù):
methodD: 支持事務(wù)(如果有)
methodD: 完成
4. MANDATORY 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.MANDATORY) public void methodE() { System.out.println("methodE: 必須加入事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodE: 完成事務(wù)"); } public void testMandatoryPropagation() { methodE(); // 調(diào)用時(shí)必須有事務(wù)存在,否則會拋出異常 } }
預(yù)期輸出:
- 如果沒有事務(wù),拋出
TransactionRequiredException
異常; - 如果有事務(wù),方法會加入現(xiàn)有事務(wù)。
5. NOT_SUPPORTED 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodF() { System.out.println("methodF: 當(dāng)前事務(wù)被掛起"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodF: 完成"); } public void testNotSupportedPropagation() { methodF(); // 如果有事務(wù),會被掛起,執(zhí)行非事務(wù)操作 } }
預(yù)期輸出:
如果方法在事務(wù)中調(diào)用,則事務(wù)會被掛起,并執(zhí)行非事務(wù)操作:
methodF: 當(dāng)前事務(wù)被掛起
methodF: 完成
6. NESTED 傳播行為示例
@Service public class UserService { @Transactional(propagation = Propagation.NESTED) public void methodG() { System.out.println("methodG: 開始嵌套事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodG: 完成嵌套事務(wù)"); } public void testNestedPropagation() { methodG(); // 方法會啟動一個(gè)嵌套事務(wù) } }
預(yù)期輸出:
methodG: 開始嵌套事務(wù)
methodG: 完成嵌套事務(wù)
五、事務(wù)傳播機(jī)制失效的場景
場景 1:同一個(gè)類中的方法直接調(diào)用
如果我們在一個(gè)類的實(shí)例中直接調(diào)用另一個(gè)被 @Transactional
注解的方法,事務(wù)傳播機(jī)制可能會失效,因?yàn)槭聞?wù)代理是基于 Spring AOP 的,而 AOP 僅對外部方法調(diào)用起作用。
示例:事務(wù)失效
@Service public class UserService { @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 開始事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事務(wù)"); } public void methodB() { System.out.println("methodB: 事務(wù)不生效(直接調(diào)用)"); methodA(); // 直接調(diào)用 methodA(),事務(wù)不會傳播 } }
問題:
methodB()
會直接調(diào)用 methodA()
,但是因?yàn)槭聞?wù)注解依賴 AOP 代理,而 methodB()
沒有通過 Spring 代理調(diào)用 methodA()
,因此事務(wù)不會生效。
預(yù)期輸出:
methodB: 事務(wù)不生效(直接調(diào)用)
methodA: 開始事務(wù)
methodA: 完成事務(wù)
事務(wù)應(yīng)該在 methodA()
中生效,但因?yàn)槭侵苯诱{(diào)用,所以沒有生效。
解決方案
為了讓事務(wù)傳播機(jī)制生效,方法應(yīng)該通過 Spring 容器中的代理對象進(jìn)行調(diào)用,可以通過 @Autowired
注入當(dāng)前類實(shí)例并調(diào)用其方法,或者通過使用外部類實(shí)例來間接調(diào)用。
@Service public class UserService { @Autowired private UserService self; // 注入當(dāng)前類的代理實(shí)例 @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 開始事務(wù)"); // 模擬數(shù)據(jù)庫操作 try { Thread.sleep(1000); // 模擬耗時(shí)操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事務(wù)"); } public void methodB() { System.out.println("methodB: 事務(wù)會生效(通過代理調(diào)用)"); self.methodA(); // 通過代理調(diào)用 methodA() } }
六、總結(jié)
Spring Boot 的事務(wù)傳播機(jī)制為開發(fā)者提供了靈活的事務(wù)管理方式,確保在復(fù)雜的業(yè)務(wù)邏輯中能夠精準(zhǔn)地控制事務(wù)的行為。通過合理選擇事務(wù)傳播行為,我們可以在多層業(yè)務(wù)邏輯中實(shí)現(xiàn)事務(wù)的一致性和隔離性。
- REQUIRED:大多數(shù)情況下使用此傳播行為,保證事務(wù)一致性。
- REQUIRES_NEW:當(dāng)需要獨(dú)立事務(wù)時(shí)使用。
- SUPPORTS 和 NOT_SUPPORTED:當(dāng)方法支持或不支持事務(wù)時(shí)使用。
- MANDATORY 和 NEVER:嚴(yán)格控制事務(wù)的參與。
- NESTED:支持嵌套事務(wù),可以獨(dú)立提交和回滾。
到此這篇關(guān)于深入理解SpringBoot事務(wù)傳播機(jī)制的文章就介紹到這了,更多相關(guān)SpringBoot事務(wù)傳播內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望
相關(guān)文章
Mybatis如何自動生成數(shù)據(jù)庫表的實(shí)體類
這篇文章主要介紹了Mybatis自動生成數(shù)據(jù)庫表的實(shí)體類的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06SpringBoot注冊Filter的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot注冊Filter的兩種實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08spring-boot-thin-launcher插件分離jar包的依賴和配置方式
這篇文章主要介紹了spring-boot-thin-launcher插件分離jar包的依賴和配置方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09使用FeignClient調(diào)用遠(yuǎn)程服務(wù)時(shí)整合本地的實(shí)現(xiàn)方法
這篇文章主要介紹了使用FeignClient調(diào)用遠(yuǎn)程服務(wù)時(shí)整合本地的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java基于Graphics2D實(shí)現(xiàn)海報(bào)制作
這篇文章主要為大家詳細(xì)介紹了Java如何基于Graphics2D實(shí)現(xiàn)海報(bào)制作,并且支持自定義顏色,背景,logo,貼圖,感興趣的小伙伴可以了解一下2024-04-04關(guān)于Mybatis實(shí)體別名支持通配符掃描問題小結(jié)
MyBatis可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄,這篇文章主要介紹了Mybatis實(shí)體別名支持通配符掃描的問題,需要的朋友可以參考下2022-01-01