Spring @Transactional 失效场景

@Transactional 是 Spring 声明式事务的核心注解,但有很多隐式失效场景,理解这些边界对正确使用事务至关重要。

失效场景详解

1. 自调用(类内部方法调用)

@Service
public class UserService {
    
    public void parentMethod() {
        // 问题:this.childMethod() 不走 AOP 代理
        this.childMethod(); // 等同于直接调用,@Transactional 失效!
    }
    
    @Transactional
    public void childMethod() {
        // 事务不会生效
    }
}

原因:Spring AOP 基于代理,只有外部调用才会走代理。this.childMethod() 是直接调用目标对象的方法,没有经过代理对象的拦截器。

解决方案

  1. 注入自身代理@Autowired UserService selfself.childMethod()
  2. 使用 AopContext((UserService) AopContext.currentProxy()).childMethod()
  3. 分离到另一个 Bean:将事务方法放到另一个 Service 中

2. private / static / final 方法

@Transactional
private void privateMethod() { } // 不生效
 
@Transactional
static void staticMethod() { }   // 不生效
 
@Transactional
final void finalMethod() { }     // CGLIB 无法重写,不生效
  • Spring AOP(JDK Proxy)只能拦截 public 方法
  • CGLIB 通过生成子类重写方法实现,final 方法无法重写

3. 异常类型不匹配

@Transactional
public void createUser() throws Exception {
    // 业务逻辑...
    throw new Exception("业务异常"); // checked exception → 不回滚!
}
  • 默认回滚规则RuntimeExceptionError 才会回滚
  • 受检异常(Checked Exception) 不回滚
  • 解决方案@Transactional(rollbackFor = Exception.class)

4. 异常被捕获吞掉

@Transactional
public void method() {
    try {
        // 业务逻辑...
        throw new RuntimeException();
    } catch (Exception e) {
        // 异常被吃掉,Spring 不知道有异常,不会回滚
        log.error("", e);
    }
}
  • 事务回滚靠异常,如果异常被捕获不抛出,Spring 认为执行成功
  • 解决方案:在 catch 中重新抛出异常

5. 数据库引擎不支持事务

-- MyISAM 引擎不支持事务,@Transactional 不生效
CREATE TABLE user_myisam (...) ENGINE=MyISAM;
  • 解决方案:确保表使用 InnoDB 引擎

6. 传播行为配置不当

@Service
public class OuterService {
    @Autowired
    InnerService innerService;
    
    @Transactional
    public void outerMethod() {
        innerService.innerMethod(); // 内层事务抛异常
    }
}
 
@Service
public class InnerService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        throw new RuntimeException(); // 内层事务回滚,但外层不一定会回滚
    }
}
  • REQUIRES_NEW 会暂停外层事务,内层异常不会导致外层回滚(除非外层也检测到异常)

7. 多数据源事务(分布式事务)

@Transactional
public void multiDataSourceMethod() {
    userDao.save(user);      // 数据源1
    orderDao.save(order);    // 数据源2
    // 一个 @Transactional 无法同时管理两个数据源的事务
}
  • 解决方案:使用 @Transactional + Atomikos / Seata / TCC 等分布式事务方案

8. 同事务内调用不同方法

  • 同一个事务中,不管调多少次内部方法,都属于同一个数据库连接和事务
  • 这通常不是”失效”,而是理解上的常见误区

正确使用检查清单

风险点检查项解决方法
自调用是否通过 this.xxx() 调用注入自身代理或拆分 Bean
方法可见性是否是 private/static/final改为 public
异常类型抛出的异常是否被回滚规则覆盖配置 rollbackFor
异常捕获异常是否被 try-catch 吞掉重新抛出异常
引擎支持数据表是否使用 InnoDB迁移到 InnoDB
传播行为内外层事务的交互是否符合预期理解 Propagation 含义
多数据源是否跨多个数据源使用分布式事务

参考链接