面试知识库
极高 进阶

Spring事务失效场景#

一句话答案#

@Transactional 失效的根因是绕过了代理或事务拦截器没有按预期工作,常见八种场景:自调用、非 public、异常被 catch、异常类型不匹配、传播行为错误、非 Spring 管理的类、多线程、数据库不支持事务。

核心要点

八种事务失效场景#

1. 自调用(同类方法调用)— 最高频#

@Service
public class OrderService {
    public void createOrder() {
        this.updateStock();  // 直接调用,不经过代理 → 事务失效
    }
    @Transactional
    public void updateStock() { ... }
}
java

原因: this.updateStock() 是直接调用,没有经过代理对象,AOP 拦截不到。 解决: 注入自己 @Autowired OrderService self; 然后调 self.updateStock(),或用 AopContext.currentProxy()

2. 非 public 方法#

原因: Spring 事务拦截器(TransactionInterceptor)主动跳过非 public 方法。

3. 异常被 catch 吞掉#

@Transactional
public void transfer() {
    try {
        deduct(); add();
    } catch (Exception e) {
        log.error("转账失败", e);  // 异常被吞,事务不回滚
    }
}
java

解决: catch 后 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),或重新抛出。

4. 异常类型不匹配#

@Transactional  // 默认只回滚 RuntimeException 和 Error
public void process() throws IOException {
    throw new IOException("文件不存在");  // 受检异常,不回滚!
}
java

解决: @Transactional(rollbackFor = Exception.class)

5. 传播行为配置错误#

Propagation.NOT_SUPPORTED / NEVER 会以非事务方式执行。

6. 非 Spring 管理的类#

没有 @Service/@Component 注解 → 不是 Spring Bean → 事务无效。

7. 多线程场景#

事务信息存在 ThreadLocal 中,新线程无法获取原事务上下文。

8. 数据库引擎不支持#

MyISAM 不支持事务,只有 InnoDB 支持。

深挖:从底层机制理解失效根因#

事务的本质:TransactionInterceptorDataSourceTransactionManager 拿一条 ConnectionsetAutoCommit(false),再由 TransactionSynchronizationManager 把这条连接绑到当前线程的 ThreadLocal;业务里所有 SQL(MyBatis/JDBC)都从 ThreadLocal 取同一条连接,最后统一 commit/rollback。理解这条主线,多数失效场景一眼看穿:

失效场景底层根因
自调用this.method() 不走代理 → TransactionInterceptor 没被触发 → 连开事务的入口都没进
非 public拦截器 computeTransactionAttribute 对非 public 方法返回 null,主动跳过
多线程连接绑在发起线程的 ThreadLocal;新线程取不到,自己新开连接(autocommit=true),与外层无关
catch 吞异常拦截器靠捕获到异常才调 rollback(),异常被吞 → 拦截器以为成功 → commit
异常类型不匹配rollbackOn() 默认只对 RuntimeException/Error 返回 true,受检异常被当作正常 → commit

一句话锚定: 失效要么是代理没介入(自调用/非 public/非 Bean),要么是拦截器拿不到回滚信号(吞异常/异常类型不匹配),要么是ThreadLocal 连接不共享(多线程)。

验证事务是否生效#

boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("事务是否激活: {}", isActive);
java
面试回答(2分钟版)

@Transactional 失效的根因是代理没有介入。最常见的是自调用——同类内 this.method() 不经过代理对象所以事务不生效,解决方法是注入自身或用 AopContext。第二个是非 public 方法,Spring 事务拦截器只处理 public。第三个是异常被 catch 吞掉了,Spring 靠异常触发回滚,catch 了就看不到。第四个是异常类型不匹配,默认只回滚 RuntimeException,受检异常需要 rollbackFor=Exception.class。还有传播行为配置为 NOT_SUPPORTED 会以非事务方式执行,多线程场景事务信息在 ThreadLocal 中不跨线程。

追问与易错

追问方向:

  • “你项目中遇到过事务失效吗?”→ 结合实际经历讲
  • “自调用怎么解决?”→ 注入自身 / AopContext / 拆分到不同 Service
  • “rollbackFor 默认值是什么?”→ RuntimeException + Error,不包含受检异常

易错点:

  • ❌ “加了 @Transactional 就有事务”——至少有八种失效场景
  • ❌ “@Transactional 可以加在 private 方法上”——不会报错但不生效
  • ❌ “catch 了异常重新 throw 就行”——throw 的必须是 rollbackFor 包含的类型