SpringBoot中事務失效的六個原因解析
SpringBoot中事務失效的原因
常見的事務失效原因包括如下六個:
1. 事務方法非public修飾
由于Spring的事務是基于AOP的方式結(jié)合動態(tài)代理來實現(xiàn)的。因此事務方法一定要是public的,這樣才能便于被Spring做事務的代理和增強。
而且,在Spring內(nèi)部也會有一個 org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource類,去檢查事務方法的修飾符:
@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. 非事務方法調(diào)用事務方法
@Service
public class OrderService {
public void createOrder(){
// ... 準備訂單數(shù)據(jù)
// 生成訂單并扣減庫存
insertOrderAndReduceStock();
}
@Transactional
public void insertOrderAndReduceStock(){
// 生成訂單
insertOrder();
// 扣減庫存
reduceStock();
}
}
可以看到,insertOrderAndReduceStock方法是一個事務方法,肯定會被Spring事務管理。Spring會給OrderService類生成一個動態(tài)代理對象,對insertOrderAndReduceStock方法做增加,實現(xiàn)事務效果。
但是現(xiàn)在createOrder方法是一個非事務方法,在其中調(diào)用了insertOrderAndReduceStock方法,這個調(diào)用其實隱含了一個this.的前綴。也就是說,這里相當于是直接調(diào)用原始的OrderService中的普通方法,而非被Spring代理對象的代理方法。那事務肯定就失效了!
3. 事務方法的異常被捕獲了
異常被捕獲了但是沒有往外拋異常,所以事務沒有發(fā)現(xiàn)方法中出現(xiàn)錯誤,所以也就沒有回滾
@Transactional
public void createOrder(){
// ... 準備訂單數(shù)據(jù)
// 生成訂單
insertOrder();
// 扣減庫存
reduceStock();
}
private void reduceStock() {
try {
// ...扣庫存
} catch (Exception e) {
// 處理異常
}
}
在這段代碼中,reduceStock方法內(nèi)部直接捕獲了Exception類型的異常,也就是說方法執(zhí)行過程中即便出現(xiàn)了異常也不會向外拋出。
而Spring的事務管理就是要感知業(yè)務方法的異常,當捕獲到異常后才會回滾事務。
現(xiàn)在事務被捕獲,就會導致Spring無法感知事務異常,自然不會回滾,事務就失效了。
4. 事務異常類型不對
@Transactional(rollbackFor = RuntimeException.class)
public void createOrder() throws IOException {
// ... 準備訂單數(shù)據(jù)
// 生成訂單
insertOrder();
// 扣減庫存
reduceStock();
throw new IOException();
}

Spring的事務管理默認感知的異常類型是RuntimeException,當事務方法內(nèi)部拋出了一個IOException時,不會被Spring捕獲,因此就不會觸發(fā)事務回滾,事務就失效了。
因此,當我們的業(yè)務中會拋出RuntimeException以外的異常時,應該通過@Transactional注解中的rollbackFor屬性來指定異常類型:
@Transactional(rollbackFor = Exception.class)
5.事務傳播行為不對
@Transactional
public void createOrder(){
// 生成訂單
insertOrder();
// 扣減庫存
reduceStock();
throw new RuntimeException("業(yè)務異常");
}
@Transactional // 默認的是如果當前沒有事務,自己創(chuàng)建事務,如果有事務則加入
public void insertOrder() {
}
// 不管當前方法所在方法有沒有都開啟一個事務
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void reduceStock() {
}
在示例代碼中,事務的入口是createOrder()方法,會開啟一個事務,可以成為外部事務。在createOrder()方法內(nèi)部又調(diào)用了insertOrder()方法和reduceStock()方法。這兩個都是事務方法。
不過,reduceStock()方法的事務傳播行為是REQUIRES_NEW,這會導致在進入reduceStock()方法時會創(chuàng)建一個新的事務,可以成為子事務。insertOrder()則是默認,因此會與createOrder()合并事務。
因此,當createOrder方法最后拋出異常時,只會導致insertOrder方法回滾,而不會導致reduceStock方法回滾,因為reduceStock是一個獨立事務。
所以,一定要慎用傳播行為,注意外部事務與內(nèi)部事務之間的關系。
6.沒有被Spring管理
即當前類沒有被SpringBoot掃描
第二種事務失效的解決方案:
上面的問題在于非事務方法中調(diào)用事務方法其中隱含了一個this.的前綴, 雖然當前方法的事務也被代理類生成了,但是因為默認關鍵字的原因,調(diào)用的還是原來的是沒有事務的方法.
所以我們現(xiàn)在要做的就是要找到被代理之后的類,然后再在方法中調(diào)用該方法
1)引入AspectJ依賴:
<!--aspecj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
2)暴露代理對象
在啟動類上添加注解,暴露代理對象:

3)使用代理對象
通過AopContext拿到當前類的代理對象,然后調(diào)用對應方法
// 返回值是Object IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy(); userCouponService.insertCouponAndCheck(userId, coupon, null);
(補充 )StringBoot中事務相關的接口
在Spring中有兩個和事務相關的接口
PlatformTransactionManager 平臺事務管理接口
作用; 對于不同的數(shù)據(jù)源采用不同的管理平臺, 常用的實現(xiàn)類如下
- DataSourceTransactionManager:使用JDBC或MyBatis進行事務管理。適用于DataSource數(shù)據(jù)源。
- HibernateTransactionManager:使用Hibernate進行事務管理。適用于Hibernate持久層框架。
TransactionDefinition 事務定義接口
TransactionDefinition 接口中定義了事務的描述相關的三類常量:
- 事務隔離級別
- 事務傳播行為
- 事務默認超時時限

1. 事務隔離級別
- DEFAULT : 采用DB默認的事務隔離級別.mysql中默認的是 REPEATABLE_READ (repeatable_read)
- READ_UNCOMMITTED: 讀未提交 未解決任何并發(fā)問題
- READ_COMMITTED: 讀已提交 解決了臟讀,存在不可重復讀和幻讀
- REPETABLE_READ : 可重復讀,解決了臟讀,不可重復讀,存在幻讀
- SERIALIZABLE :串行化.不存在并發(fā)問題
那么什么是臟讀, 幻讀和 不可重復讀呢?
臟讀(dirty read): 當一個事務讀取另一個事務尚未提交的修改時,產(chǎn)生臟讀
不可重復讀(nonrepeatable read):同一查詢在同一事務中多次進行,由于其他提交事務所做的修改或刪除,每次返回不同的結(jié)果集,此時發(fā)生不可重復讀
幻讀(phantom read):同一查詢在同一事務中多次進行,由于其他提交事務所做的插入操作,每次返回不同的結(jié)果集,此時發(fā)生幻讀
2. 事務的傳播行為
定義了七個事務的傳播行為:都是以 PROPAGATION_開頭 propagation(常用三個)
事務傳播行為是指,處于不同事務中的方法在相互調(diào)用時,執(zhí)行期間事務的維護情況
- propagation_required (spring默認的傳播行為)
- propagation_requires_new
- propagation_supports
propagation_required :
說明:指定的方法必須在事務內(nèi)執(zhí)行。若當前存在事務,就加入到當前事務中;若當前沒有事務,則創(chuàng)建一個新事務。這種傳播行為是最常見的選擇,也是 Spring 默認的事務傳播行為。
演示說明:
**如該傳播行為加在doOther()**方法上。若 doSome()方法在調(diào)用 doOther()方法時就是在事務內(nèi)運行的,則 doOther()方法的執(zhí)行也加入到該事務內(nèi)執(zhí)行。若 doSome()方法在調(diào)用 doOther()方法時沒有在事務內(nèi)執(zhí)行,則 doOther()方法會創(chuàng)建一個事務,并在其中執(zhí)行。

propagation_requires_new:
說明:總是新建一個事務,如當前存在事務,就將當前事務掛起,直到新事務執(zhí)行完畢

propagation_supports:
說明:指定的方法支持當前事務,但若當前沒有事務,也可以以非事務方法執(zhí)行

3. 事務超時時限
該值一般就是用默認值
到此這篇關于SpringBoot中事務失效的六個原因解析的文章就介紹到這了,更多相關SpringBoot事務失效原因內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot實現(xiàn)返回視圖而不是string的方法
這篇文章主要介紹了springboot實現(xiàn)返回視圖而不是string的方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Springboot 整合 Java DL4J 實現(xiàn)智能客服功能
本文主要介紹了如何使用SpringBoot整合JavaDeeplearning4j來構(gòu)建一個智能客服系統(tǒng),詳細探討了神經(jīng)網(wǎng)絡選擇、數(shù)據(jù)集格式、技術介紹、Maven依賴、代碼示例等內(nèi)容,為構(gòu)建高效、便捷、個性化的客戶服務提供了理論支持和實踐指導2024-10-10
SpringBoot整合WebSocket實現(xiàn)后端向前端主動推送消息方式
這篇文章主要介紹了SpringBoot整合WebSocket實現(xiàn)后端向前端主動推送消息方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
Java利用InputStream類實現(xiàn)文件讀取與處理
在Java開發(fā)中,輸入流(InputStream)是一個非常重要的概念,它涉及到文件讀寫、網(wǎng)絡傳輸?shù)榷鄠€方面,InputStream類是Java中輸入流的抽象基類,定義了讀取輸入流數(shù)據(jù)的方法,本文將以InputStream類為切入點,介紹Java中的輸入流概念及其應用,需要的朋友可以參考下2023-11-11

