【Spring事务必知必会】高频问题升级!7种传播行为详解+实战避坑指南

89 阅读5分钟

导语:事务传播机制是Spring事务中最容易踩坑的知识点之一!本文通过真实代码演示+日志分析,带你彻底搞懂7种传播行为,从此不再被面试官问倒!


面试官(优化版):事务传播机制到底怎么选?

通俗理解
事务传播机制就像“多个任务协作时的分工规则”——当方法A调用方法B时,B的事务该如何处理?是跟着A干,还是自己单干?

下面由"弱到强"详细讲解Spring的7种传播规则(MANDATORY抛异常->SUPORT不处理->REQUIRDE新建->REQUIRES_NEW总是新建->NOT_SUPPORTED挂起->NEVER抛异常;DESTED嵌套)。

首先是准备两个方法:

// UserService
public void addUser() {
    userMapper.insert(user);  // 插入用户
}

// BlogService
public void addBlog() {
    blogMapper.insert(blog);  // 插入博客
}


1.MANDATORY
  • 规则:必须跟着大哥干,没大哥就罢工(抛异常);如果当前存在事务,则加入该事务;如果当前没有事务,它会抛出异常。这种传播行为要求调用方法必须在一个现有的事务中运行
  • 场景:强制要求方法在事务中被调用(如敏感操作审计)
  • 代码验证
// 直接调用会抛异常!
@Transactional(propagation = Propagation.MANDATORY)
public void addBlog() {
    blogMapper.insert(blog);
}

// 没有事务的方法调用也会抛异常
public void addUser() {
    userMapper.insert(user);  // 插入用户
    blogService.addBlog();    // 调用博客服务
}

  • 异常日志
    No existing transaction found for transaction marked with propagation 'mandatory'

2. SUPPORTS
  • 规则:随遇而安,有事务就跟,没有也不强求;如果当前存在事务,方法会在这个事务中运行;如果没有事务,则方法在非事务的环境中执行。
  • 场景:查询方法适配(如根据业务决定是否走事务)
  • 踩坑点
@Transactional(propagation = Propagation.SUPPORTS)
public void addBlog() {
    blogMapper.insert(blog); // 非事务环境插入数据
    throw new RuntimeException(); // 异常不会回滚!
}

  • 现象:数据成功插入,不回滚(因为没有事务环境)

3. REQUIRED(默认)
  • 规则:有活一起干,没活自己干;默认的事务传播级别。如果当前没有事务,Spring 会创建一个新的事务;如果已经存在事务,则加入该事务。这意味着被调用的方法会在调用者的事务上下文中运行。
  • 场景:90%的通用场景(如订单创建+库存扣减)
  • 代码验证
// UserService
@Transactional(propagation = Propagation.REQUIRED)
public void addUser() {
    userMapper.insert(user);  // 插入用户
    blogService.addBlog();    // 调用博客服务
}

// BlogService
@Transactional(propagation = Propagation.REQUIRED)
public void addBlog() {
    blogMapper.insert(blog);  // 插入博客
    int i = 1/0; // 抛出异常
}

  • 日志分析

    • Creating new transaction(addUser开启事务)
    • Participating in existing transaction(addBlog加入事务)
    • 最终全部回滚(同一事务内异常导致整体失败)

4. REQUIRES_NEW
  • 规则:必须单干,暂停大哥的活;总会创建一个新的事务,如果当前存在事务,则将当前事务挂起。也就是说不管外部方法是否开启事务,REQUIRES_NEW 修饰的方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 场景:独立业务(如日志记录必须成功)
  • 代码演示
// UserService
@Transactional(propagation = Propagation.REQUIRED)
public void addUser() {
    userMapper.insert(user); 
    try {
        blogService.addBlog(); // 调用REQUIRES_NEW方法
    } catch (Exception e) {
        // 即使这里捕获异常,用户数据仍会提交!
    }
}

// BlogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addBlog() {
    blogMapper.insert(blog);
    throw new RuntimeException(); // 触发回滚
}

  • 日志关键点

    • Suspending current transaction(挂起主事务)
    • Creating new transaction(开启新事务)
    • 结果:用户数据提交成功,博客数据回滚

5. NOT_SUPPORTED
  • 规则:拒绝事务,有也暂停;以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • 场景:非核心操作(如批量数据导入)
  • 代码效果
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addBlog() {
    blogMapper.insert(blog); // 非事务执行
    throw new RuntimeException(); // 数据仍插入成功!
}


6. NEVER
  • 规则:禁止在事务中调用,否则翻脸;以非事务方式运行,如果当前存在事务,它会抛出异常。这种传播行为确保方法永远不在事务环境中运行。
  • 场景:性能敏感的非事务操作
  • 异常演示
@Transactional
public void addUser() {
    blogService.addBlog(); // 调用NEVER方法
}

// BlogService
@Transactional(propagation = Propagation.NEVER)
public void addBlog() { /*...*/ }

  • 结果:直接抛出IllegalTransactionStateException

7. NESTED
  • 规则:父子事务,子可单独回滚;如果当前存在事务,则在嵌套事务内运行。如果当前事务回滚,嵌套事务也会回滚,但嵌套事务的回滚不会导致当前事务回滚。如果当前没有事务,则其行为像REQUIRED。
  • 场景:复杂业务分层(如订单拆分子订单)
  • 代码演示
// UserService(父事务)
@Transactional
public void addUser() {
    userMapper.insert(user);
    try {
        blogService.addBlog(); // 嵌套事务
    } catch (Exception e) {
        // 子事务回滚,父事务继续
    }
}

// BlogService
@Transactional(propagation = Propagation.NESTED)
public void addBlog() {
    blogMapper.insert(blog);
    throw new RuntimeException(); 
}

  • 结果:博客数据回滚,用户数据保留

避坑指南

  1. 事务日志调试技巧
    logback.xml添加配置,实时观察事务行为:

    <logger name="org.springframework.transaction" level="DEBUG"/>
    

    运行 HTML

  2. 异常处理黄金法则

    • 受检异常(Exception)默认不回滚
    • 运行时异常(RuntimeException)默认回滚
    • 务必用rollbackFor指定需要回滚的异常类型!
  3. 自调用失效问题

    // 错误!同类调用不经过代理
    public void methodA() {
        this.methodB(); 
    }
    // 正确:通过代理对象调用
    @Autowired 
    private UserService selfProxy; // 注入自身代理
    


总结

事务传播行为选择口诀:

  • 默认就用REQUIRED
  • 独立操作REQUIRES_NEW
  • 强制校验MANDATORY
  • 非事务选NOT_SUPPORTED
  • 多层嵌套用NESTED

下期预告:《Spring事务失效的10大魔鬼场景!你中过几个?》
讨论:你在使用传播机制时遇到过哪些“坑”?欢迎留言分享!