title: @Transactional 失效场景 description: 深度解析 @Transactional 注解失效的常见原因,包括自调用、异常处理、访问权限、代理机制等核心问题及其解决方案。

@Transactional 失效场景

候选人小冯在面试京东 P6 时,面试官问道:

"@Transactional 一定会生效吗?"

小冯说:"不一定..."

面试官追问:"什么情况下会失效?"

小冯说:"private 方法...还有 self 调用..."

面试官:"还有呢?"

小冯说不出来了。

【面试官心理】 这道题我用来测试候选人对 Spring 事务的实战理解。知道 private 方法和自调用失效的人不少,但能说全所有场景的凤毛麟角。这道题能答出 5 种以上的,基本都在生产环境踩过这些坑。


一、核心问题 🔴

1.1 问题拆解

第一层:基础认知

  • "@Transactional 标注的方法一定会开启事务吗?"
  • "什么情况下 @Transactional 会失效?"

第二层:失效场景

  • "为什么 private 方法不会生效?"
  • "自调用(this.method)为什么失效?"
  • "异常被捕获后事务会怎样?"

第三层:原理分析

  • "Spring AOP 代理是怎么实现事务的?"
  • "为什么代理机制导致这些问题?"

第四层:解决方案

  • "如何解决自调用问题?"
  • "如何避免异常导致的失效?"

1.2 ❌ 错误示范

候选人原话 A:"只要标注了 @Transactional,事务就一定生效。"

问题诊断

  • 完全错误,没有任何实战经验
  • 说明没有踩过事务失效的坑
  • 完全没有理解代理机制

候选人原话 B:"private 方法不会生效,因为 Spring 代理不了 private 方法。"

问题诊断

  • 知道一个失效场景,但不知道为什么
  • 不理解 CGLIB 代理的限制

候选人原话 C:"自调用可以注入自身 Bean 来解决。"

问题诊断

  • 知道解决方案,但说不清为什么
  • 不理解 AOP 代理的原理

1.3 标准回答

P5 回答:失效场景概览

@Transactional 失效的常见场景:

失效场景原因解决方案
private 方法CGLIB 无法代理 private 方法改为 protected/public
自调用(this.method())不走代理对象注入自身 Bean
异常被捕获未重新抛出Spring 默认只对 RuntimeException 回滚使用 @Transactional(rollbackFor)
非 public 方法同 private改为 public
同一个类内部方法调用同自调用使用注入方式
异常类型不匹配默认只回滚 RuntimeException/Error配置 rollbackFor
数据库不支持事务MyISAM 引擎不支持事务改用 InnoDB

1.4 追问升级

追问 1:为什么 private 方法不生效?

这是由 CGLIB 代理机制决定的:

// CGLIB 代理原理
public class OrderService$$EnhancerByCGLIB$$12345 extends OrderService {
    // 重写所有非 final 的方法
    public void saveOrder() {
        // 这里是事务增强逻辑
        TransactionInterceptor.begin();
        try {
            super.saveOrder(); // 调用原始方法
        } finally {
            TransactionInterceptor.commit();
        }
    }

    private void saveOrderInternal() {
        // ❌ private 方法不会被重写!
        // 因为子类无法重写父类的 private 方法
        // 所以 private 方法不会被代理
    }
}

Spring 的处理:

// Spring 会在启动时检查 @Transactional 标注的方法
// 发现是 private 方法时,会打印警告日志并忽略
/*
WARN: Exception encountered during context initialization -
'@Transactional' is not supported on 'private' method
*/

追问 2:自调用为什么失效?

这是事务失效最常见的原因:

@Service
public class OrderService {
    @Transactional
    public void methodA() {
        this.methodB(); // ❌ 失效!不走代理
        // 这里的 this 是原始对象,不是代理对象
    }

    @Transactional
    public void methodB() {
        // 这里的事务不会生效
    }
}

原理图解:

外部调用链:

外部调用 orderService.methodA()
  └─ 调用代理对象 proxy.methodA()
      └─ 代理逻辑:开启事务
      └─ 调用原始对象的 this.methodA()
          └─ this 是原始对象(不是代理!)
          └─ 调用原始对象.methodB()
              └─ 不走代理,事务不生效!

正确做法:

@Service
public class OrderService {
    @Autowired
    private OrderService self; // 注入自身

    @Transactional
    public void methodA() {
        self.methodB(); // ✅ 走代理
    }

    @Transactional
    public void methodB() {
        // 事务生效
    }
}

或者:

@EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class OrderService {
    public void methodA() {
        ((OrderService) AopContext.currentProxy()).methodB();
    }

    @Transactional
    public void methodB() {
        // 事务生效
    }
}

追问 3:异常被捕获为什么不回滚?

@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountService.deduct(fromId, amount);
        accountService.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        // ❌ 异常被捕获了,没有重新抛出
        // Spring 检测不到异常,认为方法正常执行完了
        // 事务不会回滚!
    }
}

Spring 的异常处理规则:

// TransactionAspectSupport.completeTransactionAfterThrowing()
protected void doRollback(TransactionInfo txInfo, Throwable ex) {
    // Spring 默认只对 RuntimeException 和 Error 回滚
    // Checked Exception 不回滚
}

// rollbackFor 的默认规则:
// Throwable ex = ...;
// if (ex instanceof RuntimeException || ex instanceof Error) {
//     rollback();
// } else if (rollbackFor.isInstance(ex)) {
//     rollback();
// } else {
//     commit(); // 不回滚
// }

正确做法:

// 方法1:重新抛出异常
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountService.deduct(fromId, amount);
        accountService.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        throw new RuntimeException("转账失败", e); // ✅ 重新抛出
    }
}

// 方法2:配置 rollbackFor
@Transactional(rollbackFor = Exception.class) // ✅ 所有异常都回滚
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountService.deduct(fromId, amount);
        accountService.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        // Spring 会回滚,因为配置了 rollbackFor = Exception.class
    }
}

// 方法3:手动标记回滚
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountService.deduct(fromId, amount);
        accountService.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        // 手动标记回滚
        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
    }
}

追问 4:其他失效场景详解

// 场景4:同一个类内部非 public 方法调用
@Service
public class ServiceA {
    @Transactional
    public void publicMethod() {
        internalMethod(); // ❌ 同一个类内部调用,失效
    }

    @Transactional
    protected void internalMethod() {
        // 失效
    }
}

// 场景5:数据库不支持事务
// MySQL MyISAM 引擎不支持事务
@Service
public class MyService {
    @Transactional // ❌ 引擎不支持,完全无效
    public void save() {
        // MySQL 表必须是 InnoDB 引擎才支持事务
    }
}

// 场景6:传播行为导致
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method() {
    // 显式设置为不支持事务,整个方法无事务
}

// 场景7:多数据源未配置事务管理器
@Service
public class MyService {
    @Transactional("dataSource1TransactionManager")
    public void methodWithDs1() {
        // 正确指定事务管理器
    }

    @Transactional // ❌ 有多个数据源时,需要指定
    public void method() {
        // 不指定事务管理器,可能找不到默认的
    }
}

// 场景8:静态方法
@Service
public class MyService {
    @Transactional // ❌ 静态方法不会被代理
    public static void staticMethod() {
        // 失效
    }
}

// 场景9:final 方法
@Service
public class MyService {
    @Transactional // ❌ final 方法无法被重写
    public final void finalMethod() {
        // 失效
    }
}

二、延伸问题 🟡

2.1 自调用问题的深层分析

@Service
public class OrderService {
    @Autowired
    private OrderService self;

    @Transactional
    public void methodA() {
        self.methodB(); // ✅ 走代理
        self.methodC(); // ✅ 走代理
        // 但注意:methodA 和 methodB 在同一个事务中
        // 如果 methodB 抛异常,methodA 也会回滚
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 这个方法有独立的事务
        // methodA 的异常不会导致 methodB 回滚
        // methodB 的异常也不会导致 methodA 回滚
    }

    @Transactional
    public void methodC() {
        // 和 methodA 在同一个事务
        // 如果这里抛异常,整个方法 A 都会回滚
    }
}

2.2 代理对象的获取

@EnableAspectJAutoProxy(exposeProxy = true)
public class OrderService {
    @Transactional
    public void methodA() {
        OrderService proxy = (OrderService) AopContext.currentProxy();
        proxy.methodB();
    }

    @Transactional
    public void methodB() {
        // 可以通过 AopContext 获取代理对象
    }
}
⚠️

exposeProxy = true 会有性能开销,Spring Boot 2.x 默认是 false。生产环境建议使用注入自身的方式,而不是 AopContext

2.3 事务失效的排查方法

// 开启事务调试日志
logging:
  level:
    org.springframework.transaction: DEBUG
    org.springframework.jdbc.datasource: DEBUG

// 启动时会看到:
/*
Creating new transaction with name [...]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [...] for JDBC transaction
Executing prepared SQL statement [...]
Committing JDBC transaction on Connection [...]
*/

// 如果没有看到这些日志,说明事务没有开启

三、生产避坑

3.1 最常见的失效:自调用

// ❌ 错误写法(自调用)
@Service
public class OrderService {
    public void createOrder() {
        validateOrder();
        saveOrder();
        notifyUser();
    }

    @Transactional
    private void validateOrder() {
        // ❌ 私有方法 + 自调用,双重失效
    }

    @Transactional
    public void saveOrder() {
        // ❌ 自调用,事务失效
    }

    @Transactional
    public void notifyUser() {
        // ❌ 自调用,事务失效
    }
}

// ✅ 正确写法(拆分成多个 Bean)
@Service
public class OrderService {
    @Autowired
    private OrderValidator validator;
    @Autowired
    private OrderSaver saver;
    @Autowired
    private UserNotifier notifier;

    public void createOrder() {
        validator.validateOrder();
        saver.saveOrder();
        notifier.notifyUser();
    }
}

@Service
public class OrderSaver {
    @Transactional
    public void saveOrder() {
        // 事务生效
    }
}

3.2 异常处理的最佳实践

// ❌ 错误:吞掉异常
@Transactional
public void save() {
    try {
        doSomething();
    } catch (Exception e) {
        log.error("失败", e);
        // ❌ 事务不回滚!
    }
}

// ✅ 正确1:抛出异常
@Transactional
public void save() {
    try {
        doSomething();
    } catch (Exception e) {
        log.error("失败", e);
        throw e; // ✅ 抛出异常
    }
}

// ✅ 正确2:配置 rollbackFor
@Transactional(rollbackFor = Exception.class)
public void save() {
    try {
        doSomething();
    } catch (Exception e) {
        log.error("失败", e);
        // ✅ Spring 会回滚
    }
}

// ✅ 正确3:手动标记回滚
@Transactional
public void save() {
    try {
        doSomething();
    } catch (Exception e) {
        log.error("失败", e);
        // ✅ 手动标记回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

3.3 检查事务是否生效

// 方法1:查看日志
// 看到 "Creating new transaction" 日志说明事务开启

// 方法2:使用 TransactionSynchronization
@Transactional
public void save() {
    TransactionSynchronizationManager.isSynchronizationActive(); // true = 事务开启
    TransactionSynchronizationManager.isActualTransactionActive(); // true = 有实际事务
}

// 方法3:单元测试
@Test
public void testTransaction() {
    Order order = new Order();
    service.save(order);
    assertThrows(DataAccessException.class, () -> {
        // 故意触发异常
        throw new RuntimeException("test");
    });
    // 验证数据库中没有这条记录
}

四、工程选型

4.1 避免事务失效的设计原则

  1. 避免自调用:不同业务逻辑拆分到不同 Bean
  2. 使用 public 方法@Transactional 只对 public 方法生效
  3. 正确处理异常:不要随意捕获异常后不抛出
  4. 避免在事务方法中调用远程服务:远程调用超时可能导致事务长时间占用
  5. 选择合适的传播行为:不要滥用 REQUIRES_NEW

4.2 事务失效场景速查表

@Transactional 失效场景清单:

1. private 方法                                          — CGLIB 限制
2. 自调用(this.method)                                — 不走代理
3. 同一个类内部方法调用                                  — 同上
4. 异常被捕获未重新抛出                                  — Spring 检测不到异常
5. 抛出的异常不是 RuntimeException/Error                — 默认不回滚
6. 数据库不支持事务(MyISAM)                            — 引擎限制
7. 传播行为设置为 NOT_SUPPORTED/NEVER                   — 显式不使用事务
8. 非 Spring 管理的 Bean                                — 没有代理
9. 静态方法                                             — 无法被代理
10. final 方法                                           — CGLIB 无法重写
11. 多数据源未指定事务管理器                            — 找不到
12. 事务方法调用同类中另一个事务方法(需要独立事务)    — 自调用问题

五、面试总结

@Transactional 失效场景是 Spring 事务中最考察实战经验的知识点。

P5 候选人能说出 1-2 个失效场景(private 方法、自调用)。 P6 候选人能说出 4-5 个场景,能解释为什么 private 方法不生效,能说出自调用和异常处理的问题。 P7 候选人能说出所有常见场景,能画出完整的失效原因分析图,能给出实用的解决方案。

记住,事务失效不是背知识点,而是踩过坑之后的经验总结。