SpringBoot让事务管理变得极其简单,一个@Transactional注解似乎就能搞定一切。但实际开发中,事务莫名其妙不回滚的情况比比皆是。我整理了8种最常见的失效场景,每一个都是血泪教训。
一、方法不是public
@Transactional基于AOP代理,Spring默认只对public方法生效。如果你把注解加在private、protected或包级方法上,事务不会启动,也不会报错。很多人写了一个私有方法加事务,调了半天发现根本没生效。解决:确保事务方法为public。
二、自调用:this调用本类方法
这是最经典的坑。在同一个类中,方法A调用方法B,即使B加了@Transactional,事务也不会生效。因为自调用走的是原始对象,而不是代理对象。解决:把B方法抽到另一个Bean中,或者注入自身代理,或者用AopContext.currentProxy()。
三、异常被捕获,没有抛出
方法内部try-catch把异常吞了,事务管理器感知不到异常,自然不回滚。解决:捕获后要么重新抛出,要么手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。
四、异常类型不对
Spring默认只对RuntimeException和Error回滚。如果你抛的是检查异常(如IOException),事务不会回滚。解决:使用@Transactional(rollbackFor = Exception.class)。
五、数据库引擎不支持事务
MySQL的MyISAM引擎不支持事务,如果你建表时用了它,注解再正确也没用。解决:使用InnoDB引擎。这个坑在老旧项目中尤其常见。
六、多线程调用
事务信息保存在ThreadLocal中,新线程无法获取父线程的事务上下文。如果你在事务方法中开新线程执行数据库操作,新线程的操作不在同一个事务里。解决:避免在事务中开线程,或者把事务逻辑放在线程内部独立处理。
七、传播行为配置错误
@Transactional(propagation = Propagation.NOT_SUPPORTED)会挂起当前事务。如果你在需要事务的方法上配了SUPPORTS或NOT_SUPPORTED,自然不会回滚。解决:理解传播行为,默认REQUIRED通常够用。
八、Bean未被Spring管理
自己new出来的对象,即使方法上有@Transactional,也不会有代理,事务失效。比如在工具类里直接new Service()调用。解决:确保事务Bean由Spring容器管理,通过依赖注入获取。
额外补充:只读事务中写操作
@Transactional(readOnly = true)只是提示,不一定阻止写入,但某些数据库驱动会拒绝写操作,导致异常。不要把只读事务用在写方法上。
如何排查?
开启事务调试日志:logging.level.org.springframework.transaction=DEBUG。观察日志中有没有 “Creating new transaction” 或 “Participating in existing transaction”。如果没有,说明事务根本没生效。
结语
事务失效的原因大多不是Spring的bug,而是我们对代理机制、异常体系和传播行为的理解不够深。记住一个原则:事务是基于代理的,代理生效的前提是外部调用、public方法、异常抛出、Bean被管理。把这四点刻在脑子里,能避开90%的坑。