深浅主题
Spring 事务失效
问题现象
方法标有 @Transactional,但异常后数据仍存在,或者多个更新只回滚了其中一部分。
适用环境
Spring Framework 6、Spring Boot 3,默认代理模式;关系型数据库事务。
本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。
复现或诊断步骤
- 检查实例是否由 Spring 管理,入口是否经过代理,是否存在 this.save() 自调用。
- 确认异常是否被 catch 后吞掉,是否为默认不回滚的受检异常。
- 核对事务管理器、数据源、传播级别和表引擎;在测试环境记录事务是否激活。
原因分析
默认声明式事务依赖代理拦截,类内部自调用不会经过代理。默认规则一般对 RuntimeException 和 Error 回滚;受检异常需显式规则。另起线程使用独立连接,无法自动共享调用线程事务。
修复示例
java
@Service
public class OrderWriter {
private final OrderMapper mapper;
public OrderWriter(OrderMapper mapper) { this.mapper = mapper; }
@Transactional(rollbackFor = Exception.class)
public void create(Order order) throws Exception {
mapper.insert(order);
validate(order); // 业务校验失败要抛出异常
}
private void validate(Order order) throws Exception {
if (order.getAmount().signum() < 0) throw new Exception("invalid amount");
}
}让外部服务调用独立的事务服务,不依赖自注入绕过结构问题。按业务决定哪些异常应回滚,不能把所有失败静默转换为成功。需要捕获时重新抛出或明确标记回滚,同时保持接口错误语义。
验证方法
准备独立测试数据,正常执行后确认提交,分别抛出运行时和受检异常确认回滚。测试后清理指定测试记录;不要在真实订单上制造故障。
常见误区
仅观察日志中的注解不能证明事务已生效。不要假定 private、final 方法或 new 出来的对象都会被代理;不同代理类型和框架版本的可见性支持也需核对。