我總結的幾種@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。
推薦閱讀:
- Spring中@Transactional(rollbackFor=Exception.class)屬性用法介紹
- spring聲明式事務 @Transactional 不回滾的多種情況以及解決方案
- spring 中事務註解@Transactional與trycatch的使用
- Mybatis-Plus使用updateById()、update()將字段更新為null
- springBoot service層事務控制的操作