Spring 事务传播与常见失效场景
Spring 事务传播与常见失效场景
给方法加上 @Transactional 并不意味着所有调用都会自动处于事务中。注解最终要由 Spring 的事务基础设施识别,并在方法调用边界开始、提交或回滚事务。理解代理和事务管理器,才能定位“注解写了却没有回滚”的问题。
1. 声明式事务的基本过程
在常见的命令式 Spring 应用中,事务由 PlatformTransactionManager 管理。事务注解提供事务元数据,Spring AOP 在调用经过代理时执行事务拦截逻辑:获取或加入事务,调用目标方法,根据返回或异常情况提交或回滚。
1 | |
事务通常放在 Service 的业务操作边界上,因为这里最容易表达“一组数据库修改应该一起成功或失败”。
2. 自调用为什么会绕过事务
默认代理模式只拦截经过代理的外部调用。同一个对象内部用 this.otherMethod() 调用另一个标注了 @Transactional 的方法时,调用没有经过代理,内层注解可能不会生效。
优先把不同事务边界拆到独立 Spring Bean 中,通过注入后的代理调用;也可以重新设计事务边界。不要为了绕过问题随意使用 AopContext.currentProxy(),它会增加代码耦合。
3. 回滚规则
默认规则下,未处理的运行时异常和 Error 通常触发回滚;受检异常默认不一定回滚。若业务要求某类受检异常也回滚,应显式声明规则,例如:
1 | |
如果异常被方法内部捕获并吞掉,事务拦截器可能看不到异常,自然也就不会按预期回滚。应明确异常如何传播,并避免用不透明的通用异常掩盖失败原因。
4. 常见传播行为
REQUIRED:默认值;有当前事务就加入,没有就新建。REQUIRES_NEW:挂起当前事务并开启独立事务,内外事务可分别提交或回滚;需要额外连接资源。NESTED:在支持的事务管理器和资源下创建嵌套保存点;不能假设所有平台都支持。SUPPORTS、MANDATORY、NOT_SUPPORTED等选项表达其他事务存在条件,应按语义使用。
传播行为不是“异常处理开关”。选错传播方式可能让部分业务先于外层事务提交,产生需要补偿的状态。
5. 数据库事务不覆盖远程副作用
Spring 数据库事务不能自动回滚已经发出的 HTTP 请求、Redis 写入或消息发送。比如数据库事务还未提交时先调用远程服务,稍后数据库回滚,远程操作不会跟着撤销。
通常应缩短数据库事务,不要在事务内等待慢网络请求。跨服务一致性可考虑事务消息、Outbox、幂等消费和补偿流程;这些方案提供的是工程上的可靠性策略,不等于全局 ACID 事务。
6. 排查清单
- 调用是否经过 Spring 代理?是否存在自调用?
- 目标对象是否由 Spring 管理,事务管理器是否配置正确?
- 异常是否向外抛出,异常类型是否符合回滚规则?
- 多个事务方法的传播行为是否符合预期?
- 失败后检查数据库状态,而不只是看日志中有没有异常。
