我總結的幾種@Transactional失效原因說明

總結幾種@Transactional失效原因

非public方法

spring事務是通過動態代理的方法來實現的,有兩種實現動態代理的方式,jdk動態代理方式是將目標對象放入代理對象內部,通過代理對象來訪問目標對象;cglib字節碼生成是通過生成目標對象的子類,通過重寫的方式來完成對父類的增強。

但是它倆實際上可以為非public方法生成代理對象,隻不過spring在調用動態代理之前,會過濾掉非public方法。如果修改spring的源碼就可以為非public方法生成代理對象瞭。

自調用問題

若同一類中的沒有@Transactional註解的方法內部調用有@Transactional註解的方法,那麼該事務會被忽略。

原因是Spring事務是通過Spring AOP完成的,如果從外部直接訪問使用@Transactional註解的方法,那麼spring實際上會調用代理對象上的方法,在代理對象中完成事務的邏輯;

但是如果從目標對象內部調用瞭使用@Transactional註解的方法,比如在method1方法中調用瞭method2方法,method1沒有加@Transactional 註解,就算method2加瞭@Transactional 註解也沒用。因為這時會直接調用目標對象中的method1方法,進而再調用目標對象的method2方法,並沒有走代理對象導致代理失效。

異常相關問題

內部捕捉瞭異常,沒有拋出新的異常,導致事務操作不會進行回滾:

原因是spring事務源碼中是通過有沒有出現異常來判斷是否回滾的。

拋出非運行時異常

所以最好給@Transactional添加上rollbackFor=Exception.class

傳播機制配置錯誤

錯誤地使用傳播機制也會導致事務失效。如果使用瞭NOT_SUPPORTED和NEVER傳播機制,那麼事務機會失效,如果使用瞭SUPPORTS傳播機制並且當前不存在事務那麼事務也會失效。

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

@Transactional事務失效場景類內部調用實測

環境springboot2.7,mysql5.7

demo1

@Component
@Order(6)
@Slf4j
public class TestRunner implements ApplicationRunner {
 
    @Autowired
    TestService testService;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        testService.insertAndUpdate();
    }
}
@Service("testService")
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {
 
    @Override
    public boolean updateByIdOne() {
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper.eq(Test::getId,1);
        lambdaUpdateWrapper.set(Test::getName,"test");
 
        return this.update(lambdaUpdateWrapper);
    }
 
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean insertAndUpdate() {
        boolean b = this.updateByIdOne();
 
 
        Test test = new Test();
        test.setName("2222");
 
 
        boolean save = this.save(test);
        if(b){
            throw new RuntimeException("ts");
        }
        return save;
    }
}

以上代碼先跑一遍,看看拋出異常情況,能不能回滾

看庫  毫無變化

看主鍵遞增量其實是插入過瞭,我覺得事務還是生效瞭

demo2

@Service("testService")
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {
 
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean updateByIdOne() {
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper.eq(Test::getId,1);
        lambdaUpdateWrapper.set(Test::getName,"test");
        boolean update = this.update(lambdaUpdateWrapper);
 
        if(update){
            throw new RuntimeException("updateByIdOne");
        }
 
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper2 = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper2.eq(Test::getId,2);
        lambdaUpdateWrapper2.set(Test::getName,"test");
        boolean update1 = this.update(lambdaUpdateWrapper2);
 
        return update;
    }
 
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean insertAndUpdate() {
 
        Test test = new Test();
        test.setName("2222");
        boolean save = this.save(test);
 
        boolean b = this.updateByIdOne();
 
        return save;
    }
}

執行結果

子父方法都有事務註解,事務生效

demo3

@Service("testService")
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {
 
    @Override
    public boolean updateByIdOne() {
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper.eq(Test::getId,1);
        lambdaUpdateWrapper.set(Test::getName,"test");
        boolean update = this.update(lambdaUpdateWrapper);
 
        if(update){
            throw new RuntimeException("updateByIdOne");
        }
 
        return update;
    }
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean insertAndUpdate() {
 
        Test test = new Test();
        test.setName("2222");
        boolean save = this.save(test);
 
 
        boolean b = this.updateByIdOne();
        return b;
    }
}

insertAndUpdate插入成功後又回滾,update 更新成功也回滾,事務生效

demo4

@Service("testService")
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {
 
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean updateByIdOne() {
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper.eq(Test::getId,1);
        lambdaUpdateWrapper.set(Test::getName,"test");
        boolean update = this.update(lambdaUpdateWrapper);
 
        if(update){
            throw new RuntimeException("updateByIdOne");
        }
 
        LambdaUpdateWrapper<Test> lambdaUpdateWrapper2 = new UpdateWrapper<Test>().lambda();
        lambdaUpdateWrapper2.eq(Test::getId,2);
        lambdaUpdateWrapper2.set(Test::getName,"test");
        boolean update1 = this.update(lambdaUpdateWrapper2);
 
        return update;
    }
 
    @Override
    public boolean insertAndUpdate() {
        boolean b = this.updateByIdOne();
 
        return b;
    }
}

以上代碼一跑,結果就很清楚瞭。

1、在同類中調用,二個方法都有加上事務註解,生效

2、同類中,子方法有事務註解,父類方法無事務註解,在controller層調用父類方法,子方法事務不生效

3、同類中,子方法無事務註解,父類方法有事務註解,在controller層調用父類方法,之方法事務生效

這些僅為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: