首頁 > 軟體

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

2022-11-17 14:00:59

總結幾種@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層呼叫父類別方法,之方法事務生效

這些僅為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com