Spring事務(wù)中@Transactional注解不生效的原因分析與解決
1. 引言
在Spring框架中,@Transactional注解是管理數(shù)據(jù)庫事務(wù)的核心方式。然而,許多開發(fā)者在使用時會遇到一個常見問題:在同一個類中,一個方法調(diào)用另一個帶有@Transactional注解的方法時,事務(wù)并未生效。這種現(xiàn)象被稱為事務(wù)自調(diào)用失效。
本文將深入分析事務(wù)自調(diào)用的底層原理,解釋為什么事務(wù)不生效,并提供多種解決方案,幫助開發(fā)者正確使用Spring事務(wù)管理。
2. 事務(wù)自調(diào)用問題重現(xiàn)
2.1 示例代碼
@Service
public class OrderService {
public void placeOrder(Order order) {
checkInventory(order); // 檢查庫存(非事務(wù))
deductInventory(order); // 扣減庫存(事務(wù)方法)
createOrder(order); // 創(chuàng)建訂單(非事務(wù))
}
@Transactional
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
在這個例子中:
placeOrder() 是一個業(yè)務(wù)方法,調(diào)用了 deductInventory()。
deductInventory() 被標(biāo)記為 @Transactional,期望在扣減庫存時開啟事務(wù)。
2.2 問題現(xiàn)象
當(dāng) placeOrder() 調(diào)用 deductInventory() 時,事務(wù)并未生效。如果 deductInventory() 拋出異常,數(shù)據(jù)庫操作不會回滾。
3. 為什么事務(wù)自調(diào)用會失效
3.1 Spring事務(wù)的代理機制
Spring的事務(wù)管理是基于AOP(面向切面編程)實現(xiàn)的,具體來說:
- 代理模式:Spring會為帶有@Transactional的類生成一個代理對象(JDK動態(tài)代理或CGLIB代理)。
- 攔截器:代理對象會在目標(biāo)方法執(zhí)行前后添加事務(wù)管理邏輯(如開啟事務(wù)、提交或回滾)。
3.2 自調(diào)用繞過代理
public void placeOrder(Order order) {
this.deductInventory(order); // 直接調(diào)用,不走代理
}
當(dāng) placeOrder() 調(diào)用 deductInventory() 時,它使用的是 this(即當(dāng)前對象),而不是Spring生成的代理對象。
因此,事務(wù)攔截器沒有被觸發(fā),事務(wù)自然也不會生效。
4. 解決方案
4.1 方法1:拆分到不同類(推薦)
將事務(wù)方法移到另一個Service,確保調(diào)用通過代理進行:
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
public void placeOrder(Order order) {
checkInventory(order);
inventoryService.deductInventory(order); // 通過代理調(diào)用
createOrder(order);
}
}
@Service
public class InventoryService {
@Transactional
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
優(yōu)點:
- 符合單一職責(zé)原則(SRP)。
- 事務(wù)管理清晰,不會出現(xiàn)自調(diào)用問題。
4.2 方法2:使用 AopContext.currentProxy()
如果必須自調(diào)用,可以獲取當(dāng)前代理對象:
@Service
public class OrderService {
public void placeOrder(Order order) {
checkInventory(order);
((OrderService) AopContext.currentProxy()).deductInventory(order); // 通過代理調(diào)用
createOrder(order);
}
@Transactional
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
注意:
需要開啟 @EnableAspectJAutoProxy(exposeProxy = true):
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
缺點:
依賴Spring AOP機制,代碼侵入性強。
4.3 方法3:在調(diào)用方法上添加 @Transactional
如果整個流程需要事務(wù)管理,可以直接在 placeOrder() 上添加 @Transactional:
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
checkInventory(order);
deductInventory(order); // 即使自調(diào)用,外層事務(wù)仍生效
createOrder(order);
}
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
適用場景:
整個方法需要事務(wù)管理,而不是單個操作。
4.4 方法4:使用編程式事務(wù)
如果無法拆分類或修改代理設(shè)置,可以使用 TransactionTemplate:
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void placeOrder(Order order) {
checkInventory(order);
transactionTemplate.execute(status -> {
deductInventory(order); // 在事務(wù)內(nèi)執(zhí)行
return null;
});
createOrder(order);
}
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
優(yōu)點:
更靈活,可以手動控制事務(wù)邊界。
5. 最佳實踐
避免自調(diào)用:盡量將事務(wù)方法拆分到不同的類。
合理使用事務(wù):不要在事務(wù)方法中執(zhí)行耗時操作(如HTTP請求、IO操作)。
事務(wù)傳播機制:理解 @Transactional(propagation = Propagation.REQUIRED) 等選項。
異常處理:確保異常能觸發(fā)回滾(默認(rèn)僅回滾 RuntimeException)。
6. 總結(jié)
| 方案 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|
| 拆分到不同類 | 推薦方案 | 符合SRP,事務(wù)清晰 | 需要額外類 |
| AopContext.currentProxy() | 必須自調(diào)用時 | 可解決自調(diào)用問題 | 侵入性強 |
| 外層方法加 @Transactional | 整個流程需要事務(wù) | 簡單直接 | 事務(wù)范圍擴大 |
| 編程式事務(wù) | 需要精細(xì)控制 | 靈活 | 代碼冗余 |
關(guān)鍵結(jié)論:
- Spring事務(wù)基于代理,自調(diào)用會繞過代理,導(dǎo)致事務(wù)失效。
- 最佳方案是拆分事務(wù)方法到不同類,避免自調(diào)用問題。
到此這篇關(guān)于Spring事務(wù)中@Transactional注解不生效的原因分析與解決的文章就介紹到這了,更多相關(guān)Spring @Transactional注解不生效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+mybatis+thymeleaf實現(xiàn)登錄功能示例
這篇文章主要介紹了SpringBoot+mybatis+thymeleaf實現(xiàn)登錄功能示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java字符串拼接+和StringBuilder的比較與選擇
Java 提供了兩種主要的方式:使用 "+" 運算符和使用 StringBuilder 類,本文主要介紹了Java字符串拼接+和StringBuilder的比較與選擇,感興趣的可以了解一下2023-10-10
一文總結(jié)使用Java創(chuàng)建對象的八種方式
這篇文章主要介紹了8種創(chuàng)建對象的方法,包括new關(guān)鍵字、反射機制、clone()方法、反序列化、靜態(tài)工廠方法、Builder模式、匿名內(nèi)部類和方法引用,每種方法都有其適用場景和優(yōu)缺點,需要的朋友可以參考下2026-01-01
Lock、Condition實現(xiàn)簡單的生產(chǎn)者消費者模式示例
這篇文章主要介紹了Lock、Condition實現(xiàn)簡單的生產(chǎn)者消費者模式示例,需要的朋友可以參考下2014-04-04
Spring中的SpringApplicationRunListener詳細(xì)解析
這篇文章主要介紹了Spring中的SpringApplicationRunListener詳細(xì)解析,SpringApplicationRunListener是一個監(jiān)聽SpringApplication中run方法的接口,在項目啟動過程的各個階段進行事件的發(fā)布,需要的朋友可以參考下2023-11-11

