事务失效的场景


同类内部 this 调用(最常见)

@Service
public class OrderService {

    public void createOrder() {
        // this.xxx() 绕过了代理,@Transactional 形同虚设
        this.saveOrder();
    }

    @Transactional
    public void saveOrder() {
        // ...
    }
}

调用链:外部 → Proxy.createOrder() → this.saveOrder()(真实对象,非代理)→ 事务未开启

private 方法加@Transactional 注解

@Transactional  // ❌ 无意义,但不会报错,只是被忽略
private void doSomething() { ... }

这里事务失效的主要原因还是要看 Spring 实现事务的原理,是通过 AOP 来实现的,而 AOP 是通过代理来做的

  • JDK: 动态代理是代理接口方法,接口没有 private 方法
  • CGLIB: 是通过继承生成子类覆盖父类方法,但 private 方法无法被子类覆盖

方法不是 public(CGLIB 限制)

@Transactional
protected void doSomething() { ... }  // protected/default 在某些配置下也可能失效

@Transactional
void packageMethod() { ... }          // 包级私有

@Transactional 所在类未被 Spring 管理

// 没有 @Service / @Component,不是 Spring Bean
public class OrderService {
    @Transactional
    public void save() { ... }  // ❌ 无代理对象,注解无效
}

异常类型不匹配

@Transactional  // 默认只回滚 RuntimeException 和 Error
public void save() throws Exception {
    throw new Exception("checked exception");  // ❌ 不会回滚,事务提交了
}

// 正确写法
@Transactional(rollbackFor = Exception.class)
public void save() throws Exception {
    throw new Exception("checked exception");  // ✅ 会回滚
}

异常被 catch 吞掉,未重新抛出

@Transactional
public void save() {
    try {
        // ...
        dao.insert();
    } catch (Exception e) {
        log.error("error", e);
        // ❌ 没有 throw e,Spring 认为方法正常返回,提交事务
    }
}

Spring 的事务拦截器在方法返回时判断是否有异常抛出,若没有异常则提交。catch 后不重抛 = 事务拦截器看不到异常 = 提交

事务传播机制设置错误

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void save() {
    // 即使外层有事务,这里也以非事务方式运行
}

@Transactional(propagation = Propagation.NEVER)
public void save() {
    // 外层有事务时直接抛异常
}

多线程跨线程调用

@Transactional
public void save() {
    new Thread(() -> {
        // ❌ 新线程没有父线程的事务上下文(ThreadLocal 不跨线程传递)
        dao.insert();
    }).start();
}

这是最隐蔽的失效场景。事务通过 TransactionSynchronizationManager(本质是 ThreadLocal<Map>)绑定到当前线程。新线程没有这个 ThreadLocal 值,所有数据库操作以自动提交模式执行。 这也正是 OkHttp onFailure/onResponse 回调中不能直接写 @Transactional 的根本原因——它们运行在 OkHttp 的线程池线程,不是 Spring 管理的线程,ThreadLocal 事务上下文为空。但调用一个 Spring Bean 的 @Transactional 方法是可以的,因为 Spring Proxy 会在 OkHttp 的线程上新建事务并绑定到该线程的 ThreadLocal

数据库存储引擎不支持事务提交

@Transactional 和 @Async 一起使用

@Async
@Transactional
public void asyncSave() {
    // 异步执行时,@Transactional 能生效吗?
}

结论:可以生效,但是有顺序陷阱

@Async 会让方法在线程池线程上执行,Spring 代理需要在那个线程上正确建立事务上下文。实际上两者可以共存,但要注意:若 @Async 的代理先执行,调用的是真实对象还是代理对象,取决于 Spring 内部 Advisor 的排序。在 Spring Boot 中通常能正确工作,但建议分离职责——@Async 方法调用另一个 @Transactional 方法(通过代理),而不是叠加在同一个方法上。