一、Spring 事務(wù)是什么
在 Spring 框架中,事務(wù)(Transaction)是一種用于管理數(shù)據(jù)庫操作的機制,旨在確保數(shù)據(jù)的一致性、可靠性和完整性
。事務(wù)可以將一組數(shù)據(jù)庫操作(如插入、更新、刪除等)視為一個單獨的執(zhí)行單元,要么全部成功地執(zhí)行,要么全部回滾。這樣可以確保數(shù)據(jù)庫在任何時候都保持一致的狀態(tài),即使在發(fā)生故障或錯誤時也能保持?jǐn)?shù)據(jù)的完整性。
Spring 框架通過提供事務(wù)管理功能,使開發(fā)者能夠更輕松地管理事務(wù)的邊界。Spring 主要提供了兩種主要的事務(wù)管理方式:
-
編程式事務(wù)管理:通過編寫代碼顯式地管理事務(wù)的開始、提交和回滾操作。這種方式提供了更大的靈活性,但也需要更多的代碼維護。
-
聲明式事務(wù)管理:通過在配置中聲明事務(wù)的行為,由 Spring 框架自動處理事務(wù)的邊界,減少了開發(fā)者的工作量,并提高了代碼的可維護性。
二、Spring 中事務(wù)的實現(xiàn)方法
2.1 Spring 編程式事務(wù)(手動)
2.1.1 編程式事務(wù)的使用演示
在 Spring 中,編程式事務(wù)管理是一種手動控制事務(wù)邊界的方式,與 MySQL 操作事務(wù)的方法類似,它涉及三個重要的操作步驟:
-
開啟事務(wù)(獲取事務(wù)):首先需要通過獲取事務(wù)管理器(例如
DataSourceTransactionManager
)來獲取一個事務(wù),從而開始一個新的事務(wù)。事務(wù)管理器是用于管理事務(wù)的核心組件。 -
提交事務(wù):一旦一組數(shù)據(jù)庫操作成功執(zhí)行,并且希望將這些更改永久保存到數(shù)據(jù)庫中,就可以調(diào)用事務(wù)對象的提交方法。這將使得事務(wù)中的所有操作都被應(yīng)用到數(shù)據(jù)庫。
-
回滾事務(wù):如果在事務(wù)處理過程中發(fā)生錯誤或某種條件不滿足,就可以調(diào)用事務(wù)對象的回滾方法,從而撤銷事務(wù)中的所有操作,回到事務(wù)開始前的狀態(tài)。
在 Spring Boot 中,可以利用內(nèi)置的事務(wù)管理器 DataSourceTransactionManager
來獲取事務(wù),提交或回滾事務(wù)。此外,TransactionDefinition
是用來定義事務(wù)的屬性的,當(dāng)獲取事務(wù)時需要將 TransactionDefinition
傳遞進DataSourceTransactionManager
以獲取一個事務(wù)狀態(tài) TransactionStatus
。
例如,下面的代碼演示了編程式事務(wù):
@RestController
@RequestMapping("/user")
public class UserController {
// 編程式事務(wù)
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/del")
public int delById(@RequestParam("id") Integer id) {
if (id == null || id < 0) return 0;
// 1. 開啟事務(wù)
TransactionStatus transactionStatus = null;
int res = 0;
try {
transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
// 2. 業(yè)務(wù)操作 —— 刪除用戶
res = userService.delById(id);
System.out.println("刪除: " + res);
// 3. 提交、回滾事務(wù)
// 提交事務(wù)
dataSourceTransactionManager.commit(transactionStatus);
} catch (Exception e) {
e.printStackTrace();
// 回滾事務(wù)
if (transactionStatus != null) {
dataSourceTransactionManager.rollback(transactionStatus);
}
}
return res;
}
}
這段代碼展示了如何通過編程式事務(wù)管理在Spring Boot中處理用戶刪除操作。編程式事務(wù)允許我們在代碼中明確地控制事務(wù)的邊界,以及在需要時手動提交或回滾事務(wù)。
2.1.2 編程式事務(wù)存在的問題
通過上面的示例代碼可以發(fā)現(xiàn),編程式事務(wù)雖然提供了更大的靈活性,但也存在一些問題和挑戰(zhàn):
-
代碼冗余和可讀性差: 編程式事務(wù)需要在代碼中顯式地添加事務(wù)管理的邏輯,導(dǎo)致代碼變得冗余且難以維護。每次需要使用事務(wù)的地方都需要重復(fù)編寫事務(wù)開啟、提交和回滾的代碼,降低了代碼的可讀性。
-
事務(wù)邊界控制復(fù)雜: 開發(fā)者需要手動管理事務(wù)的邊界,確保事務(wù)的開始、提交和回滾都在正確的位置。這可能會導(dǎo)致遺漏事務(wù)管理的代碼,從而影響數(shù)據(jù)的一致性。
-
事務(wù)傳播和嵌套問題: 在涉及多個方法調(diào)用的場景中,手動控制事務(wù)的傳播和嵌套關(guān)系可能變得復(fù)雜。需要開發(fā)者確保事務(wù)在各個方法間正確傳播,同時處理好嵌套事務(wù)的問題。
-
異常處理繁瑣: 編程式事務(wù)需要在異常處理時手動進行回滾操作,如果異常處理不當(dāng),事務(wù)可能無法正確回滾,導(dǎo)致數(shù)據(jù)不一致。
-
可維護性差: 隨著項目的發(fā)展,業(yè)務(wù)邏輯可能會變得更加復(fù)雜,可能需要頻繁地修改事務(wù)管理的代碼。這會增加代碼維護的難度,可能導(dǎo)致錯誤的引入。
-
不利于橫向擴展: 編程式事務(wù)難以支持橫向擴展,因為事務(wù)管理的代碼緊耦合在業(yè)務(wù)邏輯中,擴展時可能需要修改大量代碼。
相比之下,聲明式事務(wù)管理通過在方法上添加注解或在配置文件中進行聲明,使事務(wù)管理與業(yè)務(wù)邏輯分離,提供了更好的代碼組織和可維護性。聲明式事務(wù)可以在切面中自動處理事務(wù)的開始、提交和回滾,從而減輕了開發(fā)者的工作負(fù)擔(dān)。
所以,大多數(shù)情況下,建議使用聲明式事務(wù)管理來處理事務(wù),特別是在簡化事務(wù)邏輯和提高代碼可讀性方面更加有效。
2.2 Spring 聲明式事務(wù)(自動)
聲明式事務(wù)的實現(xiàn)非常簡單,只需要在需要的方法上添加 @Transactional
注解就可以輕松實現(xiàn),無需手動開啟或提交事務(wù)。
- 當(dāng)進入被注解的方法時,Spring 會自動開啟一個事務(wù)。
- 方法執(zhí)行完成后,如果沒有拋出未捕獲的異常,事務(wù)會自動提交,保證數(shù)據(jù)的一致性。
- 然而,如果方法在執(zhí)行過程中發(fā)生了未經(jīng)處理的異常,事務(wù)會自動回滾,以確保數(shù)據(jù)庫的完整性和一致性。
這種方式大大簡化了事務(wù)管理的編碼,減少了手動處理事務(wù)的繁瑣操作,提高了代碼的可讀性和可維護性。例如下面的代碼實現(xiàn):
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 聲明式事務(wù)
@RequestMapping("/del")
@Transactional
public int delById(Integer id) {
if (id == null || id < 0) return 0;
int result = userService.delById(id);
return result;
}
}
在這個示例中,delById
方法使用了 @Transactional
注解,表示該方法需要受到聲明式事務(wù)的管理。在這個方法內(nèi)部,首先檢查了傳入的 id
,如果為負(fù)數(shù)則直接返回結(jié)果。然后,調(diào)用了 userService.delById(id)
方法,刪除了指定用戶。在方法結(jié)束時,事務(wù)會自動提交。
同時,如果在執(zhí)行過程中發(fā)生了未處理的異常,事務(wù)將會自動回滾,以保持?jǐn)?shù)據(jù)庫的一致性。這種方式簡化了事務(wù)管理,提高了代碼的可讀性和可維護性。
2.2.1 @Transactional 作用范圍
@Transactional
注解可以被用來修飾方法或類:
-
當(dāng)修飾方法時:需要注意它只能應(yīng)用到
public
訪問修飾符的方法上,否則注解不會生效。通常推薦在方法級別使用@Transactional
。 -
當(dāng)修飾類時:表示該注解對于類中所有的
public
方法都會生效。如果在類級別添加了@Transactional
,那么該類中所有的公共方法都將自動應(yīng)用事務(wù)管理。
一般來說,推薦將 @Transactional
注解應(yīng)用在方法級別,以便更精確地控制事務(wù)的范圍,從而避免不必要的事務(wù)開銷。如果類中的所有方法都需要事務(wù)管理,那么將注解應(yīng)用在類級別是一個更方便的選擇。
2.2.2 @Transactional 參數(shù)說明
通過查看 @Transactional
的源碼,可以發(fā)現(xiàn)它支持多個參數(shù),用來配置事務(wù)的行為。
以下是對其中參數(shù)說明:
參數(shù)名稱 | 類型 | 默認(rèn)值 | 描述 |
---|---|---|---|
value |
String | “” | 事務(wù)管理器的名稱,與 transactionManager 等效。 |
transactionManager |
String | “” | 事務(wù)管理器的名稱,與 value 等效。 |
label |
String[] | 空數(shù)組 | 事務(wù)標(biāo)簽,暫無具體用途。 |
propagation |
Propagation | Propagation.REQUIRED | 事務(wù)的傳播行為,默認(rèn)為 REQUIRED。 |
isolation |
Isolation | Isolation.DEFAULT | 事務(wù)的隔離級別,默認(rèn)為數(shù)據(jù)庫默認(rèn)隔離級別。 |
timeout |
int | -1 | 事務(wù)的超時時間,單位為秒。-1 表示沒有超時限制。 |
timeoutString |
String | “” | 事務(wù)的超時時間的字符串表示,與 timeout 等效。 |
readOnly |
boolean | false | 是否將事務(wù)設(shè)置為只讀,默認(rèn)為 false。 |
rollbackFor |
Class<? extends Throwable>[] | 空數(shù)組 | 觸發(fā)回滾的異常類型。 |
rollbackForClassName |
String[] | 空數(shù)組 | 觸發(fā)回滾的異常類型的類名字符串。 |
noRollbackFor |
Class<? extends Throwable>[] | 空數(shù)組 | 不觸發(fā)回滾的異常類型。 |
noRollbackForClassName |
String[] | 空數(shù)組 | 不觸發(fā)回滾的異常類型的類名字符串。 |
這些參數(shù)提供了對事務(wù)行為的靈活配置,可以根據(jù)具體業(yè)務(wù)需求來調(diào)整事務(wù)的傳播、隔離、超時和回滾策略等。
2.2.3 @Transactional 捕獲異常時回滾失效問題
針對于上述的實例代碼,現(xiàn)在代碼中間模擬實現(xiàn)一個異常,觀察會出現(xiàn)什么情況:
@RequestMapping("/del")
@Transactional
public int delById(Integer id) {
if (id == null || id < 0) return 0;
int result = userService.delById(id);
System.out.println(result);
try {
int num = 10 / 0;
} catch (Exception e) {
// 如果直接處理異常,則不會回滾
e.printStackTrace();
}
return result;
}
通過瀏覽器訪問,發(fā)現(xiàn)服務(wù)器成功捕獲了異常:
但是事務(wù)卻沒有回滾,對應(yīng)的用戶數(shù)據(jù)還是被刪除了:
其原因在于:
在異常處理中直接捕獲了異常并進行了處理,從而導(dǎo)致事務(wù)回滾失效。默認(rèn)情況下,@Transactional
注解會在方法內(nèi)拋出 RuntimeException
及其子類異常時觸發(fā)事務(wù)回滾。然而,當(dāng)自己在 catch
塊內(nèi)捕獲異常并處理時,Spring 無法感知到異常,從而無法觸發(fā)事務(wù)回滾。
解決方法:
對于這個問題的解決方法大致可以分為兩種:
- 將捕獲的異常再次拋出:
e.printStackTrace();
throw e;
這種方法通過重新拋出異常,使得 Spring 能夠捕獲異常并觸發(fā)事務(wù)回滾。在異常發(fā)生后,事務(wù)將被回滾,確保之前的數(shù)據(jù)庫操作不會生效,從而保持?jǐn)?shù)據(jù)的一致性。
- 使用
TransactionAspectSupport
手動回滾事務(wù):
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
這種方法利用了 Spring 提供的 TransactionAspectSupport
類來手動設(shè)置事務(wù)回滾狀態(tài)。在捕獲異常后,通過調(diào)用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
,可以將當(dāng)前事務(wù)設(shè)置為回滾狀態(tài),從而達到回滾事務(wù)的效果。這種方法更加靈活,可以在需要的時候手動控制事務(wù)的回滾。
無論選擇哪種方法,都可以在異常發(fā)生時觸發(fā)事務(wù)回滾,保障數(shù)據(jù)的完整性和一致性。選擇哪種方法取決于具體的代碼邏輯和需求。
2.4.4 @Transactional 工作原理
@Transactional
注解的工作原理基于 Spring AOP(面向切面編程)和事務(wù)管理器。它利用了 Spring 框架的代理機制來實現(xiàn)事務(wù)管理。
當(dāng)一個被 @Transactional
注解修飾的方法被調(diào)用時,Spring 會創(chuàng)建一個代理對象來包裝這個方法。代理對象會在方法執(zhí)行之前和之后添加事務(wù)管理的邏輯,以確保事務(wù)的開始、提交和回滾。這個過程是通過 AOP 技術(shù)實現(xiàn)的。
具體來說,以下是 @Transactional
注解的工作流程:
-
事務(wù)代理的創(chuàng)建: Spring 在運行時會為每個被
@Transactional
注解修飾的類創(chuàng)建一個代理對象。這個代理對象會包含事務(wù)管理的邏輯。 -
方法調(diào)用: 當(dāng)調(diào)用一個被
@Transactional
注解修飾的方法時,實際上是通過代理對象來調(diào)用。 -
事務(wù)切面的觸發(fā): 在代理對象中,事務(wù)切面會在方法執(zhí)行前后被觸發(fā)。在方法執(zhí)行前,切面會開啟一個事務(wù);在方法執(zhí)行后,切面會根據(jù)方法的執(zhí)行情況決定是提交事務(wù)還是回滾事務(wù)。
-
事務(wù)管理器的使用: 切面會通過事務(wù)管理器來控制事務(wù)。事務(wù)管理器負(fù)責(zé)實際的事務(wù)管理操作,如開啟、提交和回滾事務(wù)。
-
事務(wù)控制: 如果方法正常執(zhí)行完畢,切面會通知事務(wù)管理器提交事務(wù)。如果方法在執(zhí)行過程中拋出異常,切面會通知事務(wù)管理器回滾事務(wù)。
總體來說,@Transactional
注解的工作原理是通過代理和切面來實現(xiàn)事務(wù)管理,將事務(wù)的控制與業(yè)務(wù)邏輯分離,使代碼更加模塊化和可維護。這也是聲明式事務(wù)管理的核心機制之一。
2.3 Spring 事務(wù)失效場景
在某些情況下,Spring 中的事務(wù)可能會失效,導(dǎo)致事務(wù)不生效或不按預(yù)期執(zhí)行。以下是一些可能導(dǎo)致事務(wù)失效的場景:
-
非
public
修飾的方法: 默認(rèn)情況下,@Transactional
注解只對public
訪問修飾符的方法起作用。如果你在非public
方法上添加了@Transactional
注解,事務(wù)可能不會生效。 -
timeout
超時: 如果事務(wù)執(zhí)行的時間超過了設(shè)置的timeout
值,事務(wù)可能會被強制回滾。這可能會導(dǎo)致事務(wù)不按預(yù)期執(zhí)行,特別是當(dāng)事務(wù)需要執(zhí)行較長時間的操作時。 -
代碼中有
try/catch
: 如果在方法內(nèi)部捕獲并處理了異常,Spring 將無法感知到異常,從而無法觸發(fā)事務(wù)回滾。這可能導(dǎo)致事務(wù)在異常發(fā)生時不會回滾。 -
調(diào)用類內(nèi)部帶有
@Transactional
的方法: 當(dāng)一個類內(nèi)部的方法被調(diào)用時,它的@Transactional
注解可能不會生效。這是因為 Spring 默認(rèn)使用基于代理的事務(wù)管理,直接在類內(nèi)部調(diào)用方法不會經(jīng)過代理,從而事務(wù)管理可能不會生效。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
public int del(Integer id){
return delById(id);
}
// 聲明式事務(wù)
@RequestMapping("/del")
@Transactional
public int delById(Integer id) {
if (id == null || id < 0) return 0;
int result = userService.delById(id);
return result;
}
}
- 數(shù)據(jù)庫不支持事務(wù): 如果你的數(shù)據(jù)庫不支持事務(wù),例如使用了某些特殊的數(shù)據(jù)庫引擎,事務(wù)可能無法正常工作。在這種情況下,應(yīng)該確保使用支持事務(wù)的數(shù)據(jù)庫引擎。
三、事務(wù)的隔離級別
3.1 事務(wù)的特性回顧
在數(shù)據(jù)庫中,事務(wù)具有以下四個重要的特性,通常被稱為 ACID 特性:
-
原子性(Atomicity): 事務(wù)被視為一個不可分割的操作單元,要么全部執(zhí)行成功,要么全部失敗回滾。
-
一致性(Consistency): 事務(wù)使數(shù)據(jù)庫從一個一致的狀態(tài)轉(zhuǎn)變到另一個一致的狀態(tài),保證數(shù)據(jù)的完整性和一致性。
-
隔離性(Isolation): 并發(fā)執(zhí)行的事務(wù)之間應(yīng)該互不影響,每個事務(wù)都感覺自己在獨立地操作數(shù)據(jù)。
-
持久性(Durability): 一旦事務(wù)提交,其對數(shù)據(jù)庫的修改就應(yīng)該是永久性的,即使發(fā)生系統(tǒng)崩潰也不應(yīng)該丟失。
3.2 MySQL 的事務(wù)隔離級別
MySQL 支持以下四個事務(wù)隔離級別,用于控制多個事務(wù)之間的相互影響程度:
-
讀未提交(Read Uncommitted): 允許一個事務(wù)讀取另一個事務(wù)尚未提交的數(shù)據(jù)。這是最低的隔離級別,可能會導(dǎo)致臟讀、不可重復(fù)讀和幻讀的問題。
-
讀已提交(Read Committed): 允許一個事務(wù)只能讀取另一個事務(wù)已經(jīng)提交的數(shù)據(jù)。這可以避免臟讀,但可能會出現(xiàn)不可重復(fù)讀和幻讀的問題。
-
可重復(fù)讀(Repeatable Read): 保證在同一個事務(wù)中多次讀取同樣記錄的結(jié)果是一致的,即使其他事務(wù)對該記錄進行了修改。這可以避免臟讀和不可重復(fù)讀,但可能出現(xiàn)幻讀。
-
串行化(Serializable): 最高的隔離級別,確保每個事務(wù)都完全獨立運行,避免了臟讀、不可重復(fù)讀和幻讀問題,但可能影響并發(fā)性能。
以下是事務(wù)四個隔離級別對應(yīng)的臟讀、不可重復(fù)讀、幻讀情況:
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
讀未提交 | √ | √ | √ |
讀已提交 | × | √ | √ |
可重復(fù)讀 | × | × | √ |
串行化 | × | × | × |
- √ 表示可能出現(xiàn)該問題。
- × 表示該問題不會出現(xiàn)。
3.3 Spring 事務(wù)的隔離級別
Spring 通過 @Transactional
注解中的 isolation
參數(shù)來支持不同的事務(wù)隔離級別。Isolation
的源碼如下:
可以使用這些枚舉值來設(shè)置隔離級別:
-
Isolation.DEFAULT
:使用數(shù)據(jù)庫的默認(rèn)隔離級別。 -
Isolation.READ_UNCOMMITTED
:讀未提交。 -
Isolation.READ_COMMITTED
:讀已提交。 -
Isolation.REPEATABLE_READ
:可重復(fù)讀。 -
Isolation.SERIALIZABLE
:串行化。
例如,指定 Spring 事務(wù)的隔離級別為 DEFAULT
:
@RequestMapping("/del")
@Transactional(isolation = Isolation.DEFAULT)
public int delById(Integer id) {
if (id == null || id < 0) return 0;
int result = userService.delById(id);
return result;
}
通過選擇合適的事務(wù)隔離級別,可以在并發(fā)環(huán)境中控制事務(wù)之間的相互影響程度,從而避免數(shù)據(jù)不一致的問題。不同的隔離級別在性能和數(shù)據(jù)一致性方面有不同的權(quán)衡,開發(fā)人員需要根據(jù)具體的業(yè)務(wù)需求來選擇合適的隔離級別。
四、Spring 事務(wù)的傳播機制
4.1 為什么需要事務(wù)傳播機制
在復(fù)雜的應(yīng)用場景中,一個事務(wù)操作可能會調(diào)用多個方法或服務(wù)。這些方法可能需要獨立地進行事務(wù)管理,但又需要協(xié)同工作,以保持?jǐn)?shù)據(jù)的一致性和完整性。這時就需要引入事務(wù)傳播機制。
事務(wù)傳播機制定義了多個事務(wù)方法之間如何協(xié)同工作,如何共享同一個事務(wù),以及在嵌套事務(wù)中如何進行隔離和提交。通過事務(wù)傳播機制,可以確保多個事務(wù)方法在執(zhí)行時能夠按照一定的規(guī)則進行協(xié)調(diào),避免數(shù)據(jù)不一致的問題。
4.2 事務(wù)傳播機制的分類
Spring 定義了七種事務(wù)傳播行為,用于控制多個事務(wù)方法之間的交互。這些傳播行為可以在 @Transactional
注解中的 propagation
參數(shù)中進行設(shè)置。以下是這些傳播行為:
-
REQUIRED(默認(rèn)): 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中;如果沒有事務(wù),就創(chuàng)建一個新的事務(wù)。這是最常用的傳播行為。
-
SUPPORTS: 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中;如果沒有事務(wù),就以非事務(wù)方式執(zhí)行。
-
MANDATORY: 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中;如果沒有事務(wù),就拋出異常。
-
REQUIRES_NEW: 無論當(dāng)前是否存在事務(wù),都創(chuàng)建一個新的事務(wù)。如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。
-
NOT_SUPPORTED: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起。
-
NEVER: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),就拋出異常。
-
NESTED: 如果當(dāng)前存在事務(wù),就在一個嵌套的事務(wù)中執(zhí)行;如果沒有事務(wù),就與 REQUIRED 一樣。
以上 7 種傳播行為,可以根據(jù)是否支持當(dāng)前事務(wù)分為以下 3 類:
4.3 Spring 事務(wù)傳播機制使用案例
REQUIRED 和 NESTED 傳播機制的事務(wù)演示:
控制層 Controller
的 UserController
:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add") // /add?username=lisi&password=123456
@Transactional(propagation = Propagation.NESTED)
// Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(@RequestParam("username") String username, @RequestParam("password") String password) {
if (null == username || null == password || "".equals(username) || "".equals(password)) {
return 0;
}
int result = 0;
// 用戶添加操作
UserInfo user = new UserInfo();
user.setUsername(username);
user.setPassword(password);
result = userService.add(user);
try {
int num = 10 / 0; // 加入事務(wù):外部事務(wù)回滾,內(nèi)部事務(wù)也會回滾
} catch (Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
服務(wù)層Service
的UserService
:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private LogService logService;
public int delById(Integer id){
return userMapper.delById(id);
}
@Transactional(propagation = Propagation.NESTED)
// Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(UserInfo user){
// 添加用戶信息
int addUserResult = userMapper.add(user);
System.out.println("添加用戶結(jié)果:" + addUserResult);
//添加日志信息
Log log = new Log();
log.setMessage("添加用戶信息");
logService.add(log);
return addUserResult;
}
}
服務(wù)層Service
的LogService
:
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
// Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(Log log){
int result = logMapper.add(log);
System.out.println("添加日志結(jié)果:" + result);
// 模擬異常情況
try {
int num = 10 / 0;
} catch (Exception e) {
// 加入事務(wù):內(nèi)部事務(wù)回滾,外部事務(wù)也會回滾,并且會拋異常
// 嵌套事務(wù):內(nèi)部事務(wù)回滾,不影響外部事務(wù)
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
在事務(wù)傳播機制中,REQUIRED
和 NESTED
是兩種不同的傳播行為,它們在事務(wù)的嵌套、回滾以及對外部事務(wù)的影響等方面有所不同。通過上面代碼的演示,可以得出 REQUIRED
和 NESTED
之間的主要區(qū)別如下:
-
嵌套性質(zhì):
-
REQUIRED
:內(nèi)部方法與外部方法共享同一個事務(wù),內(nèi)部方法的事務(wù)操作是外部方法事務(wù)的一部分。 -
NESTED
:內(nèi)部方法創(chuàng)建一個嵌套事務(wù),它是外部事務(wù)的子事務(wù),具有獨立的事務(wù)狀態(tài),內(nèi)部事務(wù)的回滾不會影響外部事務(wù)。
-
-
回滾行為:
-
REQUIRED
:如果內(nèi)部方法拋出異?;蛟O(shè)置回滾,會導(dǎo)致整個外部事務(wù)回滾,包括內(nèi)部方法和外部方法的操作。 -
NESTED
:如果內(nèi)部方法拋出異?;蛟O(shè)置回滾,只會回滾內(nèi)部事務(wù),而外部事務(wù)仍然可以繼續(xù)執(zhí)行。
-
-
影響外部事務(wù):
-
REQUIRED
:內(nèi)部方法的事務(wù)操作會影響外部事務(wù)的狀態(tài),內(nèi)部方法回滾會導(dǎo)致外部事務(wù)回滾。 -
NESTED
:內(nèi)部方法的事務(wù)操作不會影響外部事務(wù)的狀態(tài),內(nèi)部方法回滾不會影響外部事務(wù)的提交或回滾。
-
-
支持性:文章來源:http://www.zghlxwxcb.cn/news/detail-651856.html
-
REQUIRED
:較為常用,適用于將多個方法的操作作為一個整體進行事務(wù)管理的情況。 -
NESTED
:在某些數(shù)據(jù)庫中不支持,需要數(shù)據(jù)庫支持保存點(Savepoint)的功能。
-
總的來說,REQUIRED
適用于需要將多個方法的操作作為一個整體事務(wù)管理的情況,而 NESTED
適用于需要在內(nèi)部方法中創(chuàng)建嵌套事務(wù)的情況,保持內(nèi)部事務(wù)的獨立性,不影響外部事務(wù)。選擇使用哪種傳播行為取決于業(yè)務(wù)需求和數(shù)據(jù)庫的支持情況。文章來源地址http://www.zghlxwxcb.cn/news/detail-651856.html
到了這里,關(guān)于【Spring】深入理解 Spring 事務(wù)及其傳播機制的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!