Spring中@Transactional失效問題


Spring中@Transactional失效

Spring中的聲明式注解@Transactional很大程度的方便了開發者進行DB數據保存。但是在一些特殊情況下,可能會造成注解不是按想定的方式生效,這里說幾種可能造成的幾種情況。

常見的幾種情況:

異常被捕獲

這是一種比較簡單不過稍不注意也可能會犯的情況。
Spring中事務提交還是回滾是根據調用的方法是否拋出異常來決定的,因此如果把異常捕獲之后又不拋出的話,即使出了問題,事務還是會提交。

@Autowired
private ClassB b;

@Autowired
private ClassC c;

@Transactional
public void methodA(){
    try {
        b.methodB();
        c.methodC();
    } catch (Exception e) {
        
    }
}

上例中,想要的結果是b.methodB()c.methodC()同時提交或回滾,但是由於異常被捕獲,即使在執行方法C的時候出現了異常,方法B的操作仍舊會生效。
(如果方法A是一次轉賬,方法B是轉賬中的加錢操作,方法C是減錢操作,B和C只執行其中一個的話會導致總金額就發生了變化)

@Transactional修飾了非public方法

這種也是有可能犯的一種情況。
@Transactional只能用於 public 的方法上,否則事務不會失效,如果要用在非 public 方法上,可以開啟AspectJ 代理模式。(默認代理模式CGlib)
CGlib是使用繼承進行動態代理的,所以理論上protect方法無修飾符時應該也可以?沒有測試,有空試一下。

同一個類中的方法調用

這是一種非常容易犯,又不容易察覺的情況。

@Component
public class A{
    @Transactional
    public void methodA() {
        methodB();
    }
    
    @Transactional
    public void methodB() {
        // do something
    }
}

上例中,methodB的事務是不生效的,因為這里是同一個類中的調用(更確切的說是同一個類同一個對象中),Spring事務的原理是調用時檢查@Transactional注解,然后生成代理類進行事務管理,但是內部調用時不會生成代理類(或者說默認不會),因此也就無法進行事務管理。

這種情況有好幾種解決方法,下面會說到。

非常見的情況

非常見是指對@Transactional進行了一些屬性的配置導致不生效。

@Transactional 注解屬性 rollbackFor 設置錯誤

rollbackFor 可以指定能夠觸發事務回滾的異常類型。Spring默認拋出了未檢查unchecked異常(繼承自 RuntimeException 的異常)或者 Error才回滾事務;其他異常不會觸發回滾事務。如果在事務中拋出其他類型的異常,但卻期望 Spring 能夠回滾事務,就需要指定rollbackFor屬性。

// 希望自定義的異常進行回滾
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

@Transactional 注解屬性 propagation 設置錯誤

這種失效是由於配置錯誤,若是錯誤的配置以下三種 propagation,事務將不會發生回滾。

  • PROPAGATION.SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • PROPAGATION.NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • PROPAGATION.NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。

propagation默認值PROPAGATION.REQUIRED:沒有事務時創建事務。有事務了則加入該事務(相當於使用一個session)

同一個類中事務方法調用解決方法

方法拆分

這是一種最簡單的方法,也就是把上面例子中的methodB拆分到一個單獨的類里面,這樣就是一般情況下的事務調用。


下面三種方法都是在methodB上添加@Transactional(propagation = Propagation.REQUIRES_NEW)之后進行的測試

使用AspectJ代理

具體操作就是,application.yml中需要配置spring.aop.auto :true,然后在啟動類開啟AspectJ代理,並暴露代理類:@EnableAspectJAutoProxy(exposeProxy = true)
這樣的話就可以在調用的時候獲取到代理類,並進行方法調用:

((TestClassA)AopContext.currentProxy()).insertB();

從ApplicationContext獲取Bean

這個原理應該是和上面一樣的,直從ApplicationContext中獲取到當前Bean,然后再調用方法:

// applicationContext 可以自動注入
applicationContext.getBean(TestClassA.class).insertB();

注入自身

@Component
public class TestClassA {
    @Autowired
    private TestClassA testClassA;
}

用這種方法也可以使methodB的事務生效,但是需要注意的是,第一次生成的代理類和自動注入的代理類不是一個對象,也就是代碼中thistestClassA不是同一個對象,並且testclassA中不會再次自動注入。

所以如果methodB插入數據時需要當前對象的屬性,這種方法便不能再使用


總之,同類事務不生效是一種很容易疏忽的情況,具體怎么處理,還是要結合業務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM