前言
- 宣告式事務是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配置檔案或者其他配置檔案中即可。
總結
- 事務失效的原因很多,但是千萬不要做到一知半解,只有深入理解了,才能在面試過程中對答如流。
- 今天的文章就到此結束了,如果覺得陳某寫得不錯,有所收穫的,關注在看來一波,你們的鼓勵,將會是我寫作的動力,謝謝支援!!!