我总结的几种@Transactional失效原因说明
作者:滕青山YYDS
总结几种@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层调用父类方法,之方法事务生效
这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。