一、數據庫事務基礎
數據庫事務(Transaction)是數據庫管理系統中用于確保數據一致性和完整性的一種機制。它是一組操作的集合,這些操作要么全部成功,要么全部失敗,從而保證數據庫狀態的正確性。
1.1 事務的基本概念
-
定義
事務是用戶定義的一個操作序列,這些操作要么全部執行,要么全部不執行。它是數據庫運行的基本單位。例如,在銀行轉賬操作中,從一個賬戶扣除金額和向另一個賬戶增加金額必須同時成功或同時失敗,這就需要通過事務來保證。
? - 事務的生命周期
-
開始事務:事務的執行開始,通常由用戶或應用程序發起。
-
執行事務:事務中的各個操作依次執行,如插入、更新、刪除等。
-
提交事務:如果事務中的所有操作都成功完成,事務被提交,所有操作對數據庫的更改將永久生效。
-
回滾事務:如果事務中的某個操作失敗,事務將被回滾,所有已經執行的操作都會被撤銷,數據庫恢復到事務開始前的狀態。
1.2 事務的特性(ACID)
事務的特性是通過 ACID 原則來保證的,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
1. 原子性(Atomicity)
-
原子性是指事務中的所有操作要么全部成功,要么全部失敗。事務是一個不可分割的最小執行單位。例如,在一個訂單系統中,創建訂單和扣款操作是一個事務。如果扣款成功但創建訂單失敗,那么整個事務會回滾,扣款操作也會被撤銷,以保證系統的狀態不會出現部分操作成功的情況。
2. 一致性(Consistency)
-
一致性是指事務執行前后,數據庫從一個一致的狀態轉換到另一個一致的狀態。事務必須保證數據庫的完整性約束沒有被破壞。例如,在一個庫存管理系統中,庫存數量不能為負。如果一個事務試圖將庫存數量減少到負數,那么這個事務應該被回滾,以保證數據庫的一致性。
3. 隔離性(Isolation)
-
隔離性是指多個并發事務之間是相互隔離的,一個事務的執行不會受到其他事務的干擾。數據庫系統提供了不同的隔離級別來控制事務之間的隔離程度。常見的隔離級別包括:
-
讀未提交(Read Uncommitted):最低的隔離級別,允許一個事務讀取另一個事務未提交的數據。這種情況下可能會出現臟讀(dirty read),即讀取到其他事務未提交的錯誤數據。
-
讀已提交(Read Committed):一個事務只能讀取到其他事務已經提交的數據,避免了臟讀。但可能會出現不可重復讀(non-repeatable read),即在同一個事務中,多次讀取同一數據可能得到不同的結果。
-
可重復讀(Repeatable Read):保證在同一個事務中,多次讀取同一數據的結果是一致的。但可能會出現幻讀(phantom read),即在同一個事務中,查詢滿足某個條件的記錄時,可能會出現新插入的記錄。
-
可串行化(Serializable):最高的隔離級別,事務之間完全隔離,按照串行的順序執行,避免了臟讀、不可重復讀和幻讀。但這種隔離級別會帶來較大的性能開銷。
-
4. 持久性(Durability)
-
持久性是指事務一旦提交,其對數據庫的更改將永久生效,即使系統發生故障也不會丟失。數據庫系統通常通過日志(log)來保證持久性。當事務提交時,數據庫會將事務的操作記錄到日志中,即使系統崩潰,也可以通過日志恢復數據。
1.3 事務的并發控制
在多用戶環境中,多個事務可能會同時對數據庫進行操作,這就需要并發控制機制來保證事務的隔離性和一致性。常見的并發控制方法包括:
1. 鎖機制
-
共享鎖(Shared Lock,S鎖):當一個事務對數據加上共享鎖后,其他事務可以讀取該數據,但不能修改它。多個事務可以同時對同一數據加共享鎖。
-
排他鎖(Exclusive Lock,X鎖):當一個事務對數據加上排他鎖后,其他事務不能對該數據加任何鎖,即不能讀取也不能修改。排他鎖用于寫操作,保證數據的獨占訪問。
-
鎖的粒度可以是行級鎖、表級鎖或數據庫級鎖。行級鎖的粒度最小,鎖的沖突概率較低,但管理開銷較大;表級鎖的粒度較大,鎖的沖突概率較高,但管理開銷較小。
2. 樂觀鎖和悲觀鎖
-
悲觀鎖(Pessimistic Locking):假設沖突很可能會發生,因此在事務開始時就對數據加鎖,直到事務結束才釋放鎖。悲觀鎖適用于寫操作較多的場景,但可能會導致鎖的沖突和等待。
-
樂觀鎖(Optimistic Locking):假設沖突較少發生,因此在事務開始時不加鎖,只有在提交時才檢查是否有沖突。如果發現沖突,則回滾事務。樂觀鎖通常通過版本號(Version Number)或時間戳(Timestamp)來實現。樂觀鎖適用于讀操作較多的場景,可以減少鎖的開銷。
1.4 事務的實現機制
數據庫系統通過日志(log)和回滾段(rollback segment)等機制來實現事務的特性。
1. 日志(Log)
-
日志記錄了事務對數據庫的所有操作,包括修改操作的前值和后值。當事務提交時,數據庫會將日志寫入磁盤,以保證持久性。如果系統發生故障,可以通過日志恢復數據。日志的寫入順序與事務的執行順序一致,因此可以保證事務的原子性和持久性。
2. 回滾段(Rollback Segment)
-
回滾段用于存儲事務執行過程中數據的舊值。當事務回滾時,數據庫可以從回滾段中恢復數據到事務開始前的狀態。回滾段的大小和數量會影響事務的性能和并發能力。
1.5 事務的使用
在實際的數據庫應用中,事務的使用通常由應用程序通過 SQL 語句來控制。
1. 顯式事務
-
顯式事務是指用戶明確地定義事務的開始和結束。例如,在 SQL 中可以使用以下語句:
-- 開始事務 BEGIN TRANSACTION;-- 執行事務中的操作 INSERT INTO table_name (column1, column2) VALUES (value1, value2); UPDATE table_name SET column1 = value1 WHERE condition;-- 提交事務 COMMIT;
如果事務中的某個操作失敗,可以通過以下語句回滾事務:
ROLLBACK;
2. 隱式事務
-
隱式事務是指數據庫系統自動為每個單獨的 SQL 語句啟動一個事務。如果語句成功執行,則事務自動提交;如果語句失敗,則事務自動回滾。隱式事務適用于簡單的數據庫操作,但對于復雜的業務邏輯,顯式事務更能保證事務的完整性和一致性。
二、@Transactional介紹
@Transactional
是 Spring 中用于聲明式事務管理的核心注解。它允許開發者通過簡單的注解方式,將事務管理邏輯與業務邏輯分離,從而簡化事務的管理。
2.1?作用
@Transactional
注解用于聲明事務的邊界。它可以讓 Spring 容器在方法執行前后自動管理事務的開啟、提交和回滾。具體來說:
-
開啟事務:在方法執行前,Spring 會創建一個新的事務(或加入已有的事務)。
-
提交事務:如果方法正常執行完成,Spring 會提交事務。
-
回滾事務:如果方法拋出異常,Spring 會根據配置決定是否回滾事務。
2.2 使用場景
@Transactional
通常用于服務層(@Service
注解的類)/ 數據庫訪問層(@Repository注解的類) 的方法上,因為事務管理通常與業務邏輯密切相關。例如:
@Service
public class UserService {@Transactionalpublic void updateUser(User user) {// 更新用戶信息userRepository.save(user);}
}
2.3. 常見屬性
@Transactional代碼如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";String[] label() default {};Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default -1;String timeoutString() default "";boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};
}
@Transactional
注解提供了多個屬性,用于配置事務的行為。以下是一些常用的屬性:
2.3.1 value/transactionManager
value和transactionManager屬性可以用來指定使用的事務管理器。
value: 這個屬性用于指定事務管理器的名稱。當你的應用中配置了多個事務管理器時,可以通過value屬性來指定使用哪一個。如果只有一個事務管理器,可以省略這個屬性。
transactionManager: 這個屬性與value屬性的作用相同,都是用于指定事務管理器的名稱。通常情況下,value和transactionManager可以互換使用,但transactionManager屬性更明確地表達了其用途。
假設你的應用中配置了兩個事務管理器,分別是transactionManagerA和transactionManagerB,你可以這樣使用@Transactional注解:
@Service
public class MyService {@Transactional("transactionManagerA")public void methodA() {// 使用 transactionManagerA}@Transactional(transactionManager = "transactionManagerB")public void methodB() {// 使用 transactionManagerB}
}
默認事務管理器: 如果沒有指定value或transactionManager,Spring會使用默認的事務管理器。默認的事務管理器通常是第一個被定義的事務管理器。
事務管理器的配置: 確保在Spring配置中正確配置了事務管理器,并且名稱與@Transactional注解中的指定名稱一致。
通過合理使用value或transactionManager屬性,可以靈活地控制不同方法或類使用不同的事務管理器,從而更好地管理事務。
2.3.2?propagation
(事務傳播行為)
在 Spring 中,@Transactional
注解的 propagation
屬性用于定義事務傳播行為,它決定了當一個事務方法被另一個事務方法調用時,事務應該如何處理。
后面我們描述外層的Transaction為父事務,內層被調用的事務為子事務。
1.?Propagation.REQUIRED
-
描述:這是?
@Transactional
?注解的默認傳播行為。如果當前存在事務,方法將加入該事務;如果當前沒有事務,會創建一個新事務。 -
示例場景:多個業務操作需要在同一個事務中完成,保證數據的一致性。比如在一個訂單處理服務中,創建訂單和扣減庫存的操作需要在同一個事務里,若其中一個操作失敗,整個事務回滾。
@Service
public class OrderService {@Autowiredprivate InventoryService inventoryService;@Transactional(propagation = Propagation.REQUIRED)public void createOrder() {// 創建訂單的業務邏輯inventoryService.reduceInventory();// 其他業務邏輯}
}@Service
public class InventoryService {@Transactional(propagation = Propagation.REQUIRED)public void reduceInventory() {// 扣減庫存的業務邏輯}
}
當調用?OrderService
?的?createOrder
?方法時,如果當前沒有事務,會創建一個新事務。調用?InventoryService
?的?reduceInventory
?方法時,由于當前存在事務,reduceInventory
?方法會加入到這個事務中。若在任何一個方法中出現異常,整個事務會回滾。
結論:
1)Propagation.REQUIRED 子事務任何一個失敗回滾,所有事務都會回滾。
2)Propagation.REQUIRED 父事務失敗回滾,所有子事務都會回滾。
2.?Propagation.NESTED
-
描述:如果當前存在事務,在嵌套事務中執行;如果當前沒有事務,和?
REQUIRED
?一樣創建新事務。嵌套事務是當前事務的子事務,有自己的保存點。當嵌套事務回滾時,不會影響外部事務,但外部事務回滾時,嵌套事務也會回滾。 -
示例場景:某些操作可以獨立回滾,但又依賴于外部事務的上下文。例如在批量處理數據時,部分數據處理失敗可以只回滾這部分操作,而不影響其他數據的處理。
-
注意:需要數據庫支持保存點(如 MySQL InnoDB、Oracle)。
@Service
public class BatchService {@Autowiredprivate SubBatchService subBatchService;@Transactional(propagation = Propagation.REQUIRED)public void batchProcess() {try {subBatchService.subProcess();} catch (Exception e) {// 處理子批量處理異常}// 其他批量處理邏輯}
}@Service
public class SubBatchService {@Transactional(propagation = Propagation.NESTED)public void subProcess() {// 子批量處理邏輯throw new RuntimeException("子批量處理異常");}
}
當調用?BatchService
?的?batchProcess
?方法時會創建一個事務,調用?SubBatchService
?的?subProcess
?方法時會創建一個嵌套事務。subProcess
?拋出異常時,subProcess
?中的操作會回滾,但?batchProcess
?中的其他操作不受影響。
結論:
1)Propagation.NESTED 子事務失敗回滾,不影響父事務的狀態。
2)Propagation.NESTED 父事務失敗回滾,所有子事務都會回滾。
3.?Propagation.REQUIRES_NEW
-
描述:無論當前是否存在事務,都會創建一個新事務,并掛起當前事務(如果存在)。新事務和當前事務相互獨立,一個事務的回滾或提交不會影響另一個事務。
-
示例場景:當某個操作需要獨立于外部事務時使用,比如記錄日志操作,即使主業務事務失敗,日志記錄也應該保存。
@Service
public class MainService {@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)public void mainOperation() {try {logService.recordLog();} catch (Exception e) {// 處理日志記錄異常}// 主業務邏輯throw new RuntimeException("主業務異常");}
}@Service
public class LogService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void recordLog() {// 記錄日志的業務邏輯}
}
調用?MainService
?的?mainOperation
?方法時會創建一個事務,調用?LogService
?的?recordLog
?方法時會掛起?mainOperation
?的事務,創建一個新事務。mainOperation
?拋出異常時,主業務事務回滾,但日志記錄事務不受影響。
結論:
1)Propagation.REQUIRES_NEW 子事務失敗回滾,不影響父事務的狀態。
2)Propagation.REQUIRES_NEW 父事務失敗回滾,不影響所有子事務狀態。
4.Propagation.SUPPORTS
-
描述:如果當前存在事務,方法將加入該事務;如果當前沒有事務,方法將以非事務方式執行。
-
示例場景:某些查詢操作可以選擇在事務中執行以保證數據的一致性,也可以在非事務環境下執行以提高性能。
@Service
public class QueryService {@Transactional(propagation = Propagation.SUPPORTS)public List<Product> getProducts() {// 查詢產品列表的業務邏輯return null;}
}
如果調用?getProducts
?方法時存在事務,它會加入該事務;如果不存在事務,它會以非事務方式執行。
5.?Propagation.NOT_SUPPORTED
-
描述:方法將以非事務方式執行,如果當前存在事務,會掛起當前事務。
-
示例場景:一些不需要事務管理的操作,如讀取配置信息等,可以避免事務帶來的開銷。
@Service
public class ConfigService {@Transactional(propagation = Propagation.NOT_SUPPORTED)public String getConfig() {// 獲取配置信息的業務邏輯return null;}
}
如果在事務環境中調用?getConfig
?方法,當前事務會被掛起,getConfig
?方法以非事務方式執行。
6.?Propagation.MANDATORY
-
描述:如果當前存在事務,方法將加入該事務;如果當前沒有事務,會拋出?
IllegalTransactionStateException
?異常。 -
示例場景:確保方法必須在一個已存在的事務中執行,例如一些數據更新操作依賴于外部事務的上下文。
@Service
public class UpdateService {@Transactional(propagation = Propagation.MANDATORY)public void updateData() {// 更新數據的業務邏輯}
}
如果在沒有事務的情況下調用?updateData
?方法,會拋出異常。
7.?Propagation.NEVER
-
描述:方法以非事務方式執行,如果當前存在事務,會拋出?
IllegalTransactionStateException
?異常。 -
示例場景:確保方法不應該在事務中執行,例如一些簡單的計算操作。
@Service
public class CalculationService {@Transactional(propagation = Propagation.NEVER)public int calculate(int a, int b) {return a + b;}
}
如果在事務環境中調用?calculate
?方法,會拋出異常。
2.3.3?isolation
(事務隔離級別)
指定事務的隔離級別,控制當前事務與其他事務之間的隔離程度。默認值為 Isolation.DEFAULT
,即數據庫默認的隔離級別。(即數據庫隔離性里面的具體分類)
隔離級別 | 描述 |
---|---|
DEFAULT | 數據庫默認的隔離級別。 |
READ_UNCOMMITTED | 讀未提交,允許并發事務讀取未提交的數據(可能出現臟讀)。 |
READ_COMMITTED | 讀已提交,允許并發事務讀取已提交的數據。 |
REPEATABLE_READ | 可重復讀,保證在同一個事務中多次讀取同一數據的結果是一致的。 |
SERIALIZABLE | 串行化,最高級別的隔離,完全隔離并發事務。 |
2.3.4?timeout
(事務超時時間)
指定事務的超時時間(以秒為單位)。如果事務執行時間超過指定值,事務將被回滾。默認值為 -1
,表示使用數據庫默認的超時時間。
2.3.5?readOnly
(只讀事務)
指定事務是否為只讀事務。如果設置為 true
,則事務不會修改數據,從而可以提高性能。默認值為 false
。
2.3.6?rollbackFor
(回滾異常類)
指定哪些異常會導致事務回滾。默認情況下,Error子類和運行時異常(RuntimeException
及其子類)會導致事務回滾,而檢查型異常不會導致事務回滾。可以通過該屬性指定額外的異常類型。
2.3.7?noRollbackFor
(不回滾異常類)
指定哪些異常不會導致事務回滾。即使這些異常是運行時異常,事務也不會回滾。
2.4. 使用方式
@Transactional
可以作用于類或方法上:
2.4.1 作用于方法
@Service
public class UserService {@Transactionalpublic void updateUser(User user) {// 更新用戶信息userRepository.save(user);}
}
2.4.2 作用于類
如果將 @Transactional
作用于類上,則該類的所有方法都將默認使用相同的事務配置。
@Transactional
@Service
public class UserService {public void updateUser(User user) {// 更新用戶信息userRepository.save(user);}public void deleteUser(Long id) {// 刪除用戶userRepository.deleteById(id);}
}
2.5 @Transactional面試問題
1)@Transactional propagation REQUIRED/NETESD/REQUIRES_NEW的區別?
2)propagation = REQUIRED, 子事務失敗的情況。看看下面代碼的執行結果是什么?
?
@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.REQUIREDorderRepository.createOrder(order);try {// Propagation.REQUIREDinventoryRepository.deductStock(order.getProductId(), order.getQuantity());} catch (Exception e) {log.error("扣減庫存失敗", e);}}
}@Repository
public class OrderRepository {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {orderMapper.insert(order);}
}@Repository
public class InventoryRepository {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void deductStock(Long productId, Integer quantity) {inventoryMapper.deductStock(productId, quantity);throw new RuntimeException("出現未知錯誤");}}
OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事務有哪些能成功提交,為什么?
三個事務都不能成功提交,因為InventoyRepository.deductStock事務因為異常會回滾,導致外層事務失敗,然后所有事務都會回滾。
詳細日志如下:?
2025-04-19T10:11:57.650+08:00 ?INFO 35024 --- [nio-8080-exec-1] com.example.controller.OrderController ? : 創建訂單:Order(id=null, orderNumber=order-004, productId=1001, quantity=100, totalAmount=180, createdAt=2025-04-18T21:50:16)
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
JDBC Connection [HikariProxyConnection@1303670389 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e79d43] will be managed by Spring
==> ?Preparing: INSERT INTO orders (order_number, product_id, quantity, total_amount) VALUES (?, ?, ?, ?)
==> Parameters: order-004(String), 1001(Long), 100(Integer), 180(BigDecimal)
<== ? ?Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868] from current transaction
==> ?Preparing: UPDATE inventory SET stock_quantity = stock_quantity - ? WHERE product_id = ?
==> Parameters: 100(Integer), 1001(Long)
<== ? ?Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
2025-04-19T10:11:57.754+08:00 ERROR 35024 --- [nio-8080-exec-1] com.example.service.OrderService ? ? ? ? : 扣減庫存失敗java.lang.RuntimeException: 出現未知錯誤
?? ?at com.example.repository.InventoryRepository.deductStock(InventoryRepository.java:31) ~[classes/:na]
?? ?at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
?? ?at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
?? ?at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
?? ?at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
?? ?at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at com.example.repository.InventoryRepository$$SpringCGLIB$$0.deductStock(<generated>) ~[classes/:na]
?? ?at com.example.service.OrderService.createOrder(OrderService.java:33) ~[classes/:na]
?? ?at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
?? ?at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
?? ?at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
?? ?at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
?? ?at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]
?? ?at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]
?? ?
?? ?
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]
2025-04-19T10:11:57.773+08:00 ERROR 35024 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] ? ?: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only] with root causeorg.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
?? ?at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:938) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:754) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
?? ?at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]
?? ?at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]
?? ?at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]
?? ?
3)progation = NESTED, 子事務失敗的情況。以下代碼的執行結果?
@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.NESTEDorderRepository.createOrder(order);try {// Propagation.NESTEDinventoryRepository.deductStock(order.getProductId(), order.getQuantity());} catch (Exception e) {log.error("扣減庫存失敗", e);}}
}@Repository
public class OrderRepository {...@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)public void createOrder(Order order) {orderMapper.insert(order);}
}@Repository
public class InventoryRepository {...@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)public void deductStock(Long productId, Integer quantity) {inventoryMapper.deductStock(productId, quantity);throw new RuntimeException("出現未知錯誤");}}
OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事務有哪些能成功提交,為什么??
OrderRepository.createOrder 成功提交
InventoryRepository.deductStock 失敗回滾
OrderService.createOrder 成功提交
因為NESTED標記的事務失敗不會影響外層事務的結果。
參考文檔:
Transaction Propagation :: Spring Framework?