Spring事務(wù)中@Transactional注解不生效的原因分析與解決
1. 引言
在Spring框架中,@Transactional注解是管理數(shù)據(jù)庫(kù)事務(wù)的核心方式。然而,許多開發(fā)者在使用時(shí)會(huì)遇到一個(gè)常見問題:在同一個(gè)類中,一個(gè)方法調(diào)用另一個(gè)帶有@Transactional注解的方法時(shí),事務(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); // 檢查庫(kù)存(非事務(wù)) deductInventory(order); // 扣減庫(kù)存(事務(wù)方法) createOrder(order); // 創(chuàng)建訂單(非事務(wù)) } @Transactional public void deductInventory(Order order) { inventoryRepository.reduceStock(order.getProductId(), order.getQuantity()); } }
在這個(gè)例子中:
placeOrder() 是一個(gè)業(yè)務(wù)方法,調(diào)用了 deductInventory()。
deductInventory() 被標(biāo)記為 @Transactional,期望在扣減庫(kù)存時(shí)開啟事務(wù)。
2.2 問題現(xiàn)象
當(dāng) placeOrder() 調(diào)用 deductInventory() 時(shí),事務(wù)并未生效。如果 deductInventory() 拋出異常,數(shù)據(jù)庫(kù)操作不會(huì)回滾。
3. 為什么事務(wù)自調(diào)用會(huì)失效
3.1 Spring事務(wù)的代理機(jī)制
Spring的事務(wù)管理是基于AOP(面向切面編程)實(shí)現(xiàn)的,具體來說:
- 代理模式:Spring會(huì)為帶有@Transactional的類生成一個(gè)代理對(duì)象(JDK動(dòng)態(tài)代理或CGLIB代理)。
- 攔截器:代理對(duì)象會(huì)在目標(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() 時(shí),它使用的是 this(即當(dāng)前對(duì)象),而不是Spring生成的代理對(duì)象。
因此,事務(wù)攔截器沒有被觸發(fā),事務(wù)自然也不會(huì)生效。
4. 解決方案
4.1 方法1:拆分到不同類(推薦)
將事務(wù)方法移到另一個(gè)Service,確保調(diào)用通過代理進(jìn)行:
@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)點(diǎn):
- 符合單一職責(zé)原則(SRP)。
- 事務(wù)管理清晰,不會(huì)出現(xiàn)自調(diào)用問題。
4.2 方法2:使用 AopContext.currentProxy()
如果必須自調(diào)用,可以獲取當(dāng)前代理對(duì)象:
@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); } }
缺點(diǎn):
依賴Spring AOP機(jī)制,代碼侵入性強(qiáng)。
4.3 方法3:在調(diào)用方法上添加 @Transactional
如果整個(gè)流程需要事務(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()); } }
適用場(chǎng)景:
整個(gè)方法需要事務(wù)管理,而不是單個(gè)操作。
4.4 方法4:使用編程式事務(wù)
如果無(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)點(diǎn):
更靈活,可以手動(dòng)控制事務(wù)邊界。
5. 最佳實(shí)踐
避免自調(diào)用:盡量將事務(wù)方法拆分到不同的類。
合理使用事務(wù):不要在事務(wù)方法中執(zhí)行耗時(shí)操作(如HTTP請(qǐng)求、IO操作)。
事務(wù)傳播機(jī)制:理解 @Transactional(propagation = Propagation.REQUIRED) 等選項(xiàng)。
異常處理:確保異常能觸發(fā)回滾(默認(rèn)僅回滾 RuntimeException)。
6. 總結(jié)
方案 | 適用場(chǎng)景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|---|
拆分到不同類 | 推薦方案 | 符合SRP,事務(wù)清晰 | 需要額外類 |
AopContext.currentProxy() | 必須自調(diào)用時(shí) | 可解決自調(diào)用問題 | 侵入性強(qiáng) |
外層方法加 @Transactional | 整個(gè)流程需要事務(wù) | 簡(jiǎn)單直接 | 事務(wù)范圍擴(kuò)大 |
編程式事務(wù) | 需要精細(xì)控制 | 靈活 | 代碼冗余 |
關(guān)鍵結(jié)論:
- Spring事務(wù)基于代理,自調(diào)用會(huì)繞過代理,導(dǎo)致事務(wù)失效。
- 最佳方案是拆分事務(wù)方法到不同類,避免自調(diào)用問題。
到此這篇關(guān)于Spring事務(wù)中@Transactional注解不生效的原因分析與解決的文章就介紹到這了,更多相關(guān)Spring @Transactional注解不生效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springcloud連接遠(yuǎn)程nacos失敗顯示localhost服務(wù)連接失敗的問題解決
這篇文章主要介紹了springcloud連接遠(yuǎn)程nacos失敗顯示localhost服務(wù)連接失敗的問題解決,文中有詳細(xì)的代碼示例供大家參考,對(duì)大家解決問題有一定的幫助,需要的朋友可以參考下2024-03-03Spring Security登錄接口兼容JSON格式登錄實(shí)現(xiàn)示例
前后端分離中,前端和后端的數(shù)據(jù)交互通常是JSON格式,本文主要介紹了Spring Security登錄接口兼容JSON格式登錄實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01SpringBoot實(shí)現(xiàn)連接nacos并支持多環(huán)境部署
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)連接nacos并支持多環(huán)境部署方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Spring Security 使用 OncePerRequestFilter
OncePerRequestFilter是一個(gè)過濾器,每個(gè)請(qǐng)求都會(huì)執(zhí)行一次;一般開發(fā)中主要是做檢查是否已登錄、Token是否過期和授權(quán)等操作,而每個(gè)操作都是一個(gè)過濾器,下面介紹Spring Security 使用 OncePerRequestFilter 過濾器校驗(yàn)登錄過期、請(qǐng)求日志等操作方法,感興趣的朋友一起看看吧2024-06-06java編程兩種樹形菜單結(jié)構(gòu)的轉(zhuǎn)換代碼
這篇文章主要介紹了java編程兩種樹形菜單結(jié)構(gòu)的轉(zhuǎn)換代碼,首先介紹了兩種樹形菜單結(jié)構(gòu)的代碼,然后展示了轉(zhuǎn)換器實(shí)例代碼,最后分享了相關(guān)實(shí)例及結(jié)果演示,具有一定借鑒價(jià)值,需要的朋友可以了解下。2017-12-12springboot項(xiàng)目啟動(dòng)類錯(cuò)誤(找不到或無(wú)法加載主類 com.**Application)
本文主要介紹了spring-boot項(xiàng)目啟動(dòng)類錯(cuò)誤(找不到或無(wú)法加載主類 com.**Application),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05Eclipse下使用ANT編譯提示OutOfMemory的解決方法
由于需要使用ANT編譯的代碼比較多,特別是在第一次變異的時(shí)候,會(huì)出現(xiàn)OutOfMemory錯(cuò)誤。并提示更改ANT_OPTS設(shè)定。2009-04-04一文詳解Java中的反射與new創(chuàng)建對(duì)象
Java中的反射(Reflection)和使用new關(guān)鍵字創(chuàng)建對(duì)象是兩種不同的對(duì)象創(chuàng)建方式,各有優(yōu)缺點(diǎn)和適用場(chǎng)景,本文小編給大家詳細(xì)介紹了Java中的反射與new創(chuàng)建對(duì)象,感興趣的小伙伴跟著小編一起來看看吧2024-07-07