前言
- 聲明式事務是Spring功能中最爽之一,可是有些時候,我們在使用聲明式事務並未生效,這是為什么呢?
- 文章首發於微信公眾號【碼猿技術專欄】
- 今天陳某帶大家來聊一聊聲明事務的幾種失效場景。本文將會從以下兩個方面來說一下事務為什么會失效?
- @Transactional介紹
- @Transactional失效場景
@Transactional介紹
@Transactional
是聲明式事務的注解,可以被標記在類上
、接口
、方法
上。
- 該注解中有很多值得深入了解的幾種屬性,我們來看一下。
transactionManager
- 指定事務管理器,值為
bean
的名稱,這個主要用於多事務管理器情況下指定。比如多數據源配置的情況下。
isolation
- 事務的隔離級別,默認是
Isolation.DEFAULT
。
- 幾種值的含義如下:
Isolation.DEFAULT
:事務默認的隔離級別,使用數據庫默認的隔離級別。
Isolation.READ_UNCOMMITTED
:這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻讀。
Isolation.READ_COMMITTED
:保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻讀。
Isolation.REPEATABLE_READ
:這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻讀。
Isolation.SERIALIZABLE
:這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻讀。
propagation
- 代表事務的傳播行為,默認值為
Propagation.REQUIRED
。
Propagation.REQUIRED
:如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。比如A方法內部調用了B方法,此時B方法將會使用A方法的事務。
Propagation.MANDATORY
:支持當前事務,如果當前沒有事務,就拋出異常。
Propagation.NEVER
:以非事務方式執行,如果當前存在事務,則拋出異常。
Propagation.NOT_SUPPORTED
:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
Propagation.REQUIRES_NEW
:新建事務,如果當前存在事務,把當前事務掛起。比如A方法使用默認的事務傳播屬性,B方法使用REQUIRES_NEW
,此時A方法在內部調用B方法,一旦A方法出現異常,A方法中的事務回滾了,但是B方法並沒有回滾,因為A和B方法使用的不是同一個事務,B方法新建了一個事務。
Propagation.NESTED
:支持當前事務,新增Savepoint
點,也就是在進入子事務之前,父事務建立一個回滾點,與當前事務同步提交或回滾。 子事務是父事務的一部分,在父事務還未提交時,子事務一定沒有提交。嵌套事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。
timeout
readOnly
- 該屬性用於設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。如果一個事務只涉及到只讀,可以設置為true。
rollbackFor 屬性
- 用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。
- 默認是在
RuntimeException
和Error
上回滾。
noRollbackFor
- 拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。
@Transactional失效場景
- 聲明式事務失效的場景有很多,陳某這里只是羅列一下幾種常見的場景。
底層數據庫引擎不支持事務
- 如果數據庫引擎不支持事務,則Spring自然無法支持事務。
在非public修飾的方法使用
- @Transactional注解使用的是AOP,在使用動態代理的時候只能針對
public
方法進行代理,源碼依據在AbstractFallbackTransactionAttributeSource
類中的computeTransactionAttribute
方法中,如下:
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
- 此處如果不是標注在
public
修飾的方法上並不會拋出異常,但是會導致事務失效。
異常被 " 踹死了 "
- 這種情況小白是最容易犯錯的,在整個事務的方法中使用
try-catch
,導致異常無法拋出,自然會導致事務失效。偽代碼如下:
@Transactional
public void method(){
try{
//插入一條數據
//更改一條數據
}catch(Exception ex){
return;
}
}
方法中調用同類的方法
- 簡單的說就是一個類中的
A方法
(未標注聲明式事務)在內部調用了B方法
(標注了聲明式事務),這樣會導致B方法中的事務失效。
- 代碼如下:
public class Test{
public void A(){
//插入一條數據
//調用B方法
B();
}
@Transactional
public void B(){
//插入數據
}
}
- 為什么會失效呢?:其實原因很簡單,Spring在掃描Bean的時候會自動為標注了
@Transactional
注解的類生成一個代理類(proxy),當有注解的方法被調用的時候,實際上是代理類調用的,代理類在調用之前會開啟事務,執行事務的操作,但是同類中的方法互相調用,相當於this.B()
,此時的B方法並非是代理類調用,而是直接通過原有的Bean直接調用,所以注解會失效。
- 如何解決呢?:這就涉及到注解失效的原因了,后續文章會介紹到,這里不過多介紹了。
rollbackFor屬性設置錯誤
- 很容易理解,指定異常觸發回滾,一旦設置錯誤,導致一些異常不能觸發回滾,此時的聲明式事務不就失效了嗎。
noRollbackFor屬性設置錯誤
- 這個和rollbackFor屬性設置錯誤類似,一旦設置錯誤,也會導致異常不能觸發回滾,此時的聲明式事務會失效。
propagation屬性設置錯誤
- 事務的傳播屬性在上面已經介紹了,默認的事務傳播屬性是
Propagation.REQUIRED
,但是一旦配置了錯誤的傳播屬性,也是會導致事務失效,如下三種配置將會導致事務失效:
- Propagation.SUPPORTS
- Propagation.NOT_SUPPORTED
- Propagation.NEVER
原始SSM項目,重復掃描導致事務失效
- 在原始的SSM項目中都配置了
context:component-scan
並且同時掃描了service層,此時事務將會失效。
- 按照Spring配置文件的加載順序來說,會先加載Springmvc的配置文件,如果在加載Springmvc配置文件的時候把service也加載了,但是此時事務還沒加載,將會導致事務無法成功生效。
- 解決方法很簡單,把掃描service層的配置設置在Spring配置文件或者其他配置文件中即可。
總結
- 事務失效的原因很多,但是千萬不要做到一知半解,只有深入理解了,才能在面試過程中對答如流。
- 今天的文章就到此結束了,如果覺得陳某寫得不錯,有所收獲的,關注在看來一波,你們的鼓勵,將會是我寫作的動力,謝謝支持!!!