SpringBoot中事務(wù)失效的六個原因解析
SpringBoot中事務(wù)失效的原因
常見的事務(wù)失效原因包括如下六個:
1. 事務(wù)方法非public修飾
由于Spring的事務(wù)是基于AOP的方式結(jié)合動態(tài)代理來實(shí)現(xiàn)的。因此事務(wù)方法一定要是public的,這樣才能便于被Spring做事務(wù)的代理和增強(qiáng)。
而且,在Spring內(nèi)部也會有一個 org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource類,去檢查事務(wù)方法的修飾符:
@Nullable protected TransactionAttribute computeTransactionAttribute( Method method, @Nullable Class<?> targetClass) { // Don't allow non-public methods, as configured. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // ... 略 return null; }
2. 非事務(wù)方法調(diào)用事務(wù)方法
@Service public class OrderService { public void createOrder(){ // ... 準(zhǔn)備訂單數(shù)據(jù) // 生成訂單并扣減庫存 insertOrderAndReduceStock(); } @Transactional public void insertOrderAndReduceStock(){ // 生成訂單 insertOrder(); // 扣減庫存 reduceStock(); } }
可以看到,insertOrderAndReduceStock方法是一個事務(wù)方法,肯定會被Spring事務(wù)管理。Spring會給OrderService類生成一個動態(tài)代理對象,對insertOrderAndReduceStock方法做增加,實(shí)現(xiàn)事務(wù)效果。
但是現(xiàn)在createOrder方法是一個非事務(wù)方法,在其中調(diào)用了insertOrderAndReduceStock方法,這個調(diào)用其實(shí)隱含了一個this.的前綴。也就是說,這里相當(dāng)于是直接調(diào)用原始的OrderService中的普通方法,而非被Spring代理對象的代理方法。那事務(wù)肯定就失效了!
3. 事務(wù)方法的異常被捕獲了
異常被捕獲了但是沒有往外拋異常,所以事務(wù)沒有發(fā)現(xiàn)方法中出現(xiàn)錯誤,所以也就沒有回滾
@Transactional public void createOrder(){ // ... 準(zhǔn)備訂單數(shù)據(jù) // 生成訂單 insertOrder(); // 扣減庫存 reduceStock(); } private void reduceStock() { try { // ...扣庫存 } catch (Exception e) { // 處理異常 } }
在這段代碼中,reduceStock方法內(nèi)部直接捕獲了Exception類型的異常,也就是說方法執(zhí)行過程中即便出現(xiàn)了異常也不會向外拋出。
而Spring的事務(wù)管理就是要感知業(yè)務(wù)方法的異常,當(dāng)捕獲到異常后才會回滾事務(wù)。
現(xiàn)在事務(wù)被捕獲,就會導(dǎo)致Spring無法感知事務(wù)異常,自然不會回滾,事務(wù)就失效了。
4. 事務(wù)異常類型不對
@Transactional(rollbackFor = RuntimeException.class) public void createOrder() throws IOException { // ... 準(zhǔn)備訂單數(shù)據(jù) // 生成訂單 insertOrder(); // 扣減庫存 reduceStock(); throw new IOException(); }
Spring的事務(wù)管理默認(rèn)感知的異常類型是RuntimeException,當(dāng)事務(wù)方法內(nèi)部拋出了一個IOException時(shí),不會被Spring捕獲,因此就不會觸發(fā)事務(wù)回滾,事務(wù)就失效了。
因此,當(dāng)我們的業(yè)務(wù)中會拋出RuntimeException以外的異常時(shí),應(yīng)該通過@Transactional注解中的rollbackFor屬性來指定異常類型:
@Transactional(rollbackFor = Exception.class)
5.事務(wù)傳播行為不對
@Transactional public void createOrder(){ // 生成訂單 insertOrder(); // 扣減庫存 reduceStock(); throw new RuntimeException("業(yè)務(wù)異常"); } @Transactional // 默認(rèn)的是如果當(dāng)前沒有事務(wù),自己創(chuàng)建事務(wù),如果有事務(wù)則加入 public void insertOrder() { } // 不管當(dāng)前方法所在方法有沒有都開啟一個事務(wù) @Transactional(propagation = Propagation.REQUIRES_NEW) public void reduceStock() { }
在示例代碼中,事務(wù)的入口是createOrder()方法,會開啟一個事務(wù),可以成為外部事務(wù)。在createOrder()方法內(nèi)部又調(diào)用了insertOrder()方法和reduceStock()方法。這兩個都是事務(wù)方法。
不過,reduceStock()方法的事務(wù)傳播行為是REQUIRES_NEW,這會導(dǎo)致在進(jìn)入reduceStock()方法時(shí)會創(chuàng)建一個新的事務(wù),可以成為子事務(wù)。insertOrder()則是默認(rèn),因此會與createOrder()合并事務(wù)。
因此,當(dāng)createOrder方法最后拋出異常時(shí),只會導(dǎo)致insertOrder方法回滾,而不會導(dǎo)致reduceStock方法回滾,因?yàn)閞educeStock是一個獨(dú)立事務(wù)。
所以,一定要慎用傳播行為,注意外部事務(wù)與內(nèi)部事務(wù)之間的關(guān)系。
6.沒有被Spring管理
即當(dāng)前類沒有被SpringBoot掃描
第二種事務(wù)失效的解決方案:
上面的問題在于非事務(wù)方法中調(diào)用事務(wù)方法其中隱含了一個this.的前綴, 雖然當(dāng)前方法的事務(wù)也被代理類生成了,但是因?yàn)槟J(rèn)關(guān)鍵字的原因,調(diào)用的還是原來的是沒有事務(wù)的方法.
所以我們現(xiàn)在要做的就是要找到被代理之后的類,然后再在方法中調(diào)用該方法
1)引入AspectJ依賴:
<!--aspecj--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
2)暴露代理對象
在啟動類上添加注解,暴露代理對象:
3)使用代理對象
通過AopContext拿到當(dāng)前類的代理對象,然后調(diào)用對應(yīng)方法
// 返回值是Object IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy(); userCouponService.insertCouponAndCheck(userId, coupon, null);
(補(bǔ)充 )StringBoot中事務(wù)相關(guān)的接口
在Spring中有兩個和事務(wù)相關(guān)的接口
PlatformTransactionManager 平臺事務(wù)管理接口
作用; 對于不同的數(shù)據(jù)源采用不同的管理平臺, 常用的實(shí)現(xiàn)類如下
- DataSourceTransactionManager:使用JDBC或MyBatis進(jìn)行事務(wù)管理。適用于DataSource數(shù)據(jù)源。
- HibernateTransactionManager:使用Hibernate進(jìn)行事務(wù)管理。適用于Hibernate持久層框架。
TransactionDefinition 事務(wù)定義接口
TransactionDefinition 接口中定義了事務(wù)的描述相關(guān)的三類常量:
- 事務(wù)隔離級別
- 事務(wù)傳播行為
- 事務(wù)默認(rèn)超時(shí)時(shí)限
1. 事務(wù)隔離級別
- DEFAULT : 采用DB默認(rèn)的事務(wù)隔離級別.mysql中默認(rèn)的是 REPEATABLE_READ (repeatable_read)
- READ_UNCOMMITTED: 讀未提交 未解決任何并發(fā)問題
- READ_COMMITTED: 讀已提交 解決了臟讀,存在不可重復(fù)讀和幻讀
- REPETABLE_READ : 可重復(fù)讀,解決了臟讀,不可重復(fù)讀,存在幻讀
- SERIALIZABLE :串行化.不存在并發(fā)問題
那么什么是臟讀, 幻讀和 不可重復(fù)讀呢?
臟讀(dirty read): 當(dāng)一個事務(wù)讀取另一個事務(wù)尚未提交的修改時(shí),產(chǎn)生臟讀
不可重復(fù)讀(nonrepeatable read):同一查詢在同一事務(wù)中多次進(jìn)行,由于其他提交事務(wù)所做的修改或刪除,每次返回不同的結(jié)果集,此時(shí)發(fā)生不可重復(fù)讀
幻讀(phantom read):同一查詢在同一事務(wù)中多次進(jìn)行,由于其他提交事務(wù)所做的插入操作,每次返回不同的結(jié)果集,此時(shí)發(fā)生幻讀
2. 事務(wù)的傳播行為
定義了七個事務(wù)的傳播行為:都是以 PROPAGATION_開頭 propagation(常用三個)
事務(wù)傳播行為是指,處于不同事務(wù)中的方法在相互調(diào)用時(shí),執(zhí)行期間事務(wù)的維護(hù)情況
- propagation_required (spring默認(rèn)的傳播行為)
- propagation_requires_new
- propagation_supports
propagation_required :
說明:指定的方法必須在事務(wù)內(nèi)執(zhí)行。若當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中;若當(dāng)前沒有事務(wù),則創(chuàng)建一個新事務(wù)。這種傳播行為是最常見的選擇,也是 Spring 默認(rèn)的事務(wù)傳播行為。
演示說明:
**如該傳播行為加在doOther()**方法上。若 doSome()方法在調(diào)用 doOther()方法時(shí)就是在事務(wù)內(nèi)運(yùn)行的,則 doOther()方法的執(zhí)行也加入到該事務(wù)內(nèi)執(zhí)行。若 doSome()方法在調(diào)用 doOther()方法時(shí)沒有在事務(wù)內(nèi)執(zhí)行,則 doOther()方法會創(chuàng)建一個事務(wù),并在其中執(zhí)行。
propagation_requires_new:
說明:總是新建一個事務(wù),如當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起,直到新事務(wù)執(zhí)行完畢
propagation_supports:
說明:指定的方法支持當(dāng)前事務(wù),但若當(dāng)前沒有事務(wù),也可以以非事務(wù)方法執(zhí)行
3. 事務(wù)超時(shí)時(shí)限
該值一般就是用默認(rèn)值
到此這篇關(guān)于SpringBoot中事務(wù)失效的六個原因解析的文章就介紹到這了,更多相關(guān)SpringBoot事務(wù)失效原因內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中容器的創(chuàng)建流程詳細(xì)解讀
這篇文章主要介紹了Spring中容器的創(chuàng)建流程詳細(xì)解讀,Spring?框架其本質(zhì)是作為一個容器,提供給應(yīng)用程序需要的對象,了解容器的誕生過程,有助于我們理解?Spring?框架,也便于我們“插手”這個過程,需要的朋友可以參考下2023-10-10Java中StringBuilder常用構(gòu)造方法解析
這篇文章主要介紹了Java中StringBuilder常用構(gòu)造方法解析,StringBuilder是一個可標(biāo)的字符串類,我們可以吧它看成是一個容器這里的可變指的是StringBuilder對象中的內(nèi)容是可變的,需要的朋友可以參考下2024-01-01Springboot實(shí)現(xiàn)對配置文件中的明文密碼加密詳解
我們在SpringBoot項(xiàng)目當(dāng)中,會把數(shù)據(jù)庫的用戶名密碼等配置直接放在yaml或者properties文件中,這樣維護(hù)數(shù)據(jù)庫的密碼等敏感信息顯然是有一定風(fēng)險(xiǎn)的。所以本文為大家整理了對配置文件中的明文密碼加密的方法,希望對大家有所幫助2023-03-03java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList
這篇文章主要介紹了java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Java多線程中的ThreadLocal應(yīng)用場景及問題解讀
這篇文章主要介紹了Java多線程中的ThreadLocal應(yīng)用場景及問題解讀,ThreadLocal這個類在多線程并發(fā)中主要的使用場景是什么呢,我們都知道多線程并發(fā)問題實(shí)際就是多個線程對公共資源訪問和修改問題,需要的朋友可以參考下2023-12-12