面試官:你知道哪些事務失效的場景?

愛撒謊的男孩發表於2020-04-19

前言

  • 宣告式事務是Spring功能中最爽之一,可是有些時候,我們在使用宣告式事務並未生效,這是為什麼呢?
  • 文章首發於微信公眾號【碼猿技術專欄】
  • 今天陳某帶大家來聊一聊宣告事務的幾種失效場景。本文將會從以下兩個方面來說一下事務為什麼會失效?
    1. @Transactional介紹
    2. @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 屬性

  • 用於指定能夠觸發事務回滾的異常型別,可以指定多個異常型別。
  • 預設是在RuntimeExceptionError上回滾。

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配置檔案或者其他配置檔案中即可。

總結

  • 事務失效的原因很多,但是千萬不要做到一知半解,只有深入理解了,才能在面試過程中對答如流。
  • 今天的文章就到此結束了,如果覺得陳某寫得不錯,有所收穫的,關注在看來一波,你們的鼓勵,將會是我寫作的動力,謝謝支援!!!

相關文章