Skip to content

Spring 事务失效 ​

问题现象 ​

方法标有 @Transactional,但异常后数据仍存在,或者多个更新只回滚了其中一部分。

适用环境 ​

Spring Framework 6、Spring Boot 3,默认代理模式;关系型数据库事务。

本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。

复现或诊断步骤 ​

  1. 检查实例是否由 Spring 管理,入口是否经过代理,是否存在 this.save() 自调用。
  2. 确认异常是否被 catch 后吞掉,是否为默认不回滚的受检异常。
  3. 核对事务管理器、数据源、传播级别和表引擎;在测试环境记录事务是否激活。

原因分析 ​

默认声明式事务依赖代理拦截,类内部自调用不会经过代理。默认规则一般对 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 出来的对象都会被代理;不同代理类型和框架版本的可见性支持也需核对。

官方参考链接 ​

相关分类:Java 后端 · 全部笔记