1. 程式人生 > 其它 >Spring事務不生效問題

Spring事務不生效問題

事務未生效可能造成嚴重的資料不一致性問題,因而保證事務生效至關重要。Spring事務是通過Spring aop實現的,所以不生效的本質問題是spring aop沒生效,或者說沒有代理成功,所以有必要了解下spring aop。

spring事務不生效場景

  • 非public修飾方法
    • private修飾
      @Transactional
      private void save(){...}
      呼叫save方法,事務不生效
    • protected修飾
      @Transactional
      protected void save(){...}
      呼叫save方法,事務不生效
  • final修飾的public法或類
    • final修飾方法
      @Transactional
      public final void save(){...}
      呼叫save方法,事務不生效
    • final修飾類
      @Transactional
      public final class UserService{...}
      呼叫UserService的方法,事務不生效
  • 被代理物件內部方法呼叫
    • 非事務方法呼叫內部事務方法
      public void save(){
      insert()
      }
      @Transactional
      public void insert(){...}
      此時呼叫save()方法,insert()上的事務不生效
    • 事務方法呼叫內部事務方法
      @Transactional
      public void save(){
      insert()
      }
      @Transactional
      public void insert(){...}
      此時呼叫save()方法,insert()上的事務不生效,save()方法正常呼叫時(非內部方法呼叫)事務生效

Spring aop的簡單介紹

spring aop支援動態代理(執行時代理)和靜態代理(編譯期織入)

  • 基本概念

    • target物件
      目標物件,即想要代理的目標。
    • proxy
      代理物件,訪問目標物件的方法,需要通過代理物件,無法直接訪問目標物件。
      代理物件可能允許,最終呼叫目標物件方法,也可能不允許。
  • 靜態代理(Aspectj)
    1.該實現需要使用特殊編譯器,一般不使用,spring只是提供相應的整合實現。
    2.使用動態代理已經可以完成80%以上的需求了。
    3.本文也是基於動態代理的情況下討論事務不生效情況的。
    4.因此在此不展開詳細討論。

  • 動態代理(CGLIB&JDK)

    • JDK動態代理
      1.JDK動態代理通過實現相同介面實現代理(Proxy)
      2.spring管理的Bean中是對應的是Proxy
      3.Proxy物件中包含target物件
      4.目標方法可以執行時,總是呼叫target的原生方法
      5.事務aop預設publicMethodsOnly(該配置未提供直接修改-AnnotationTransactionAttributeSource)
    • CGLIB動態代理
      1.CGLIB動態代理通過繼承目標類生成子類作為代理
      2.spring管理的Bean中是對應的是Proxy
      3.Proxy物件中包含target物件
      4.目標方法可以執行時,總是呼叫target的原生方法
      5.事務aop預設publicMethodsOnly(該配置未提供直接修改-AnnotationTransactionAttributeSource)

Spring 事務不生效情況(即aop不生效情況),原因解析

  • 無法代理
    • 方法使用非public修飾(protected、private等等)
      @Transactional
      private void save(){...}

      @Transactional
      protected void save(){...}
      spring 事務aop預設publicMethodsOnly,即只有public修飾的方法,會被代理,private和protected修飾的方法save()無法被代理,事務無效。
    • 方法使用final修飾
      @Transactional
      public final void save(){...}
      CGLIB動態代理通過生成子類的方式代理,final方法無法重寫
    • 類使用final修飾
      @Transactional
      public final class UserService{...}
      CGLIB動態代理通過生成子類的方式代理,final類無法生成子類
  • 未通過proxy物件呼叫
    • 非事務方法呼叫內部事務方法
      public void save(){
      insert()
      }
      @Transactional
      public void insert(){...}
      該情況隱含使用this關鍵字,因此被代理物件的內部事務方法insert()直接被呼叫,而不經過代理物件;
      內部方法insert()上的事務無效。
    • 事務方法呼叫內部事務方法
      @Transactional
      public void save(){
      insert()
      }
      @Transactional
      public void insert(){...}
      該情況隱含使用this關鍵字,因此被代理物件的內部事務方法insert()直接被呼叫,而不經過代理物件;
      內部方法insert()上的事務無效,但是事務方法save()正確呼叫時(通過代理物件呼叫),save()方法的事務生效。

除錯輔助

當無法通過程式碼分析確定是否有事務(aop是否代理成功)時,可以在開發環境除錯確認

  • 事務AOP
    在配置了expose-proxy=true時,呼叫靜態方法TransactionAspectSupport.currentTransactionStatus()可以獲取當前事務資訊;
    該資訊儲存在ThreadLocal型別的執行緒變數;不存在時會拋異常,所以使用時需要try-catch。
  • 其他AOP
    在配置了expose-proxy=true時,呼叫靜態方法AopContext.currentProxy()可以獲取當前代理物件;該物件儲存在ThreadLocal型別的執行緒變數;不存在時會拋異常,所以使用時需要try-catch。