面試官:你知道哪些事務失效的場景?
- 2020 年 4 月 19 日
- 筆記
- spring, springboot, 碼猿技術專欄, 程式設計師
前言
- 聲明式事務是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配置文件或者其他配置文件中即可。
總結
- 事務失效的原因很多,但是千萬不要做到一知半解,只有深入理解了,才能在面試過程中對答如流。
- 今天的文章就到此結束了,如果覺得陳某寫得不錯,有所收穫的,關注在看來一波,你們的鼓勵,將會是我寫作的動力,謝謝支援!!!