Spring 事务传播行为详解

Spring 事务传播行为详解

事务传播行为(Propagation)定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。这是 Spring 事务属性中最核心、也最容易混淆的概念。

一、什么是传播行为

事务传播行为回答的问题是:方法 A 开启了事务,方法 B 也被 @Transactional 标注,当 A 调用 B 时,B 是加入 A 的事务,还是新建一个事务,还是以非事务方式执行?

Spring 定义了 7 种传播行为,定义在 Propagation 枚举中。

二、7 种传播行为总览

传播行为 含义 当前有事务 当前无事务
REQUIRED 默认,加入或新建 加入当前事务 新建事务
REQUIRES_NEW 总是新建独立事务 挂起当前,新建 新建事务
NESTED 嵌套事务(保存点) 创建保存点 新建事务
SUPPORTS 支持当前事务 加入当前事务 非事务执行
NOT_SUPPORTED 不支持事务 挂起当前,非事务执行 非事务执行
MANDATORY 强制要求事务 加入当前事务 抛异常
NEVER 禁止事务 抛异常 非事务执行

三、逐一详解

1. REQUIRED(默认)

语义:如果当前有事务,就加入;如果没有,就新建一个。

这是最常用的传播行为,也是默认值。绝大多数业务方法都用它。

java 复制代码
@Service
public class OrderService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderDao.save(order);
        // 调用另一个 REQUIRED 方法,加入当前事务
        inventoryService.decrease(order.getProductId());
    }
}

@Service
public class InventoryService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void decrease(Long productId) {
        // 加入 OrderService 的事务
    }
}

关键点 :createOrder 和 decrease 在同一个事务中。任何一方抛异常,整个事务回滚。

2. REQUIRES_NEW

语义:总是新建一个独立事务。如果当前有事务,挂起当前事务,新建一个,执行完后再恢复当前事务。

java 复制代码
@Service
public class OrderService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderDao.save(order);
        // 日志用独立事务,主业务回滚不影响日志
        logService.saveLog("创建订单:" + order.getId());
    }
}

@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String message) {
        logDao.save(message);
    }
}

关键点:

  • saveLog 在独立事务中执行,不受 createOrder 事务影响。
  • 即使 createOrder 后续抛异常回滚,saveLog 的记录已经提交,不会回滚。
  • 两个事务使用不同的数据库连接。

典型场景:日志记录、审计操作、消息发送------这些操作不应该因为主业务失败而丢失。

3. NESTED

语义:如果当前有事务,在当前事务中创建一个保存点(Savepoint),嵌套事务执行。如果当前没有事务,则新建一个事务。

java 复制代码
@Service
public class OrderService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderDao.save(order);
        try {
            // 嵌套事务,失败只回滚到保存点
            inventoryService.decrease(order.getProductId());
        } catch (Exception e) {
            log.error("扣减库存失败,但订单保留", e);
        }
    }
}

@Service
public class InventoryService {
    
    @Transactional(propagation = Propagation.NESTED)
    public void decrease(Long productId) {
        // 如果这里抛异常,只回滚到保存点
        // 外部事务可以继续执行
    }
}

关键点:

  • NESTED 是外部事务的一部分,通过保存点实现部分回滚。
  • 内部事务失败,回滚到保存点,外部事务可以选择继续或回滚。
  • 外部事务失败,内部事务也会一起回滚。
  • NESTED 依赖数据库的保存点支持,MySQL InnoDB 支持。

典型场景:部分回滚,如批量处理中允许某些子操作失败而不影响整体。

4. SUPPORTS

语义:如果当前有事务,就加入;如果没有,就以非事务方式执行。

java 复制代码
@Service
public class QueryService {
    
    @Transactional(propagation = Propagation.SUPPORTS)
    public User findById(Long id) {
        return userDao.findById(id);
    }
}

典型场景:查询方法,有事务就用事务,没有也能正常执行。

5. NOT_SUPPORTED

语义:以非事务方式执行。如果当前有事务,挂起当前事务。

java 复制代码
@Service
public class NotifyService {
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void sendNotification(String message) {
        // 非事务执行,不占用数据库连接
    }
}

典型场景:不需要事务的操作,如发送消息、调用外部接口。

6. MANDATORY

语义 :必须在一个已有事务中执行,否则抛出 IllegalTransactionStateException。

java 复制代码
@Service
public class InventoryService {
    
    @Transactional(propagation = Propagation.MANDATORY)
    public void decrease(Long productId) {
        // 必须在事务中调用,否则报错
    }
}

典型场景:强制要求调用方开启事务,确保数据一致性。

7. NEVER

语义:必须以非事务方式执行,如果当前有事务,抛出异常。

java 复制代码
@Service
public class AuditService {
    
    @Transactional(propagation = Propagation.NEVER)
    public void audit(String message) {
        // 不允许在事务中调用
    }
}

典型场景:明确禁止事务的操作。

四、核心对比:REQUIRED vs REQUIRES_NEW vs NESTED

对比维度 REQUIRED REQUIRES_NEW NESTED
事务数量 1 个 2 个独立事务 1 个事务 + 保存点
数据库连接 共用 各自独立 共用
内部回滚 整体回滚 不影响外部 回滚到保存点
外部回滚 内部一起回滚 不影响内部 内部一起回滚
依赖保存点 否 否 是
适用场景 大多数业务 日志、审计 部分回滚

用一张图说明:

复制代码
REQUIRED:
  [============ 同一个事务 ============]
  外部方法          内部方法
  任一失败 → 整体回滚

REQUIRES_NEW:
  [==== 外部事务 ====]
       [==== 内部事务 ====]
  外部失败 → 内部已提交,不回滚
  内部失败 → 外部可继续

NESTED:
  [============ 外部事务 ============]
       [== 保存点 ==]
  内部失败 → 回滚到保存点,外部可继续
  外部失败 → 全部回滚

五、实际案例

案例:下单 + 扣库存 + 记录日志

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private OrderDao orderDao;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private LogService logService;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        // 1. 保存订单(主事务)
        orderDao.save(order);
        
        // 2. 扣减库存(加入主事务)
        inventoryService.decrease(order.getProductId(), order.getQuantity());
        
        // 3. 记录日志(独立事务,主事务回滚日志也保留)
        logService.saveLog("订单创建:" + order.getId());
    }
}

@Service
public class InventoryService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void decrease(Long productId, int quantity) {
        // 加入主事务,失败则整个订单回滚
        inventoryDao.decrease(productId, quantity);
    }
}

@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String message) {
        // 独立事务,不受主事务回滚影响
        logDao.save(message);
    }
}

执行结果:

  • 订单保存成功、库存扣减成功 → 全部提交。
  • 库存扣减失败 → 订单回滚,但日志已提交(独立事务)。
  • 订单保存失败 → 库存和订单都回滚,日志保留。

六、常见坑

1. 内部调用传播行为失效

同一个类中方法 A 调用方法 B,B 的传播行为不生效。因为内部调用不经过代理。

java 复制代码
@Service
public class OrderService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        this.saveLog();  // ❌ 传播行为不生效
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog() {
        // 不会新建独立事务
    }
}

解决方案:注入自身代理,或把方法移到另一个 Bean 中。

2. REQUIRES_NEW 占用额外连接

REQUIRES_NEW 会挂起当前事务,新建一个数据库连接。如果并发量高,可能耗尽连接池。使用时要评估连接池大小。

3. NESTED 依赖数据库支持

NESTED 依赖数据库的保存点(Savepoint)功能。MySQL InnoDB 支持,但某些数据库或驱动可能不支持。使用前确认数据库支持。

4. 异常被吞掉导致传播行为不符预期

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
    try {
        innerService.doSomething();  // REQUIRES_NEW
    } catch (Exception e) {
        // 异常被吞掉,外部事务不会回滚
    }
}

如果内部是 REQUIRES_NEW 且异常被吞掉,内部事务已回滚,外部事务继续执行。这可能是期望的行为,也可能不是,取决于业务需求。

七、总结

传播行为 一句话说明 常用度
REQUIRED 加入或新建,默认 ⭐⭐⭐⭐⭐
REQUIRES_NEW 总是新建独立事务 ⭐⭐⭐⭐
NESTED 嵌套事务,支持部分回滚 ⭐⭐⭐
SUPPORTS 有则加入,无则非事务 ⭐⭐⭐
NOT_SUPPORTED 非事务执行,挂起当前 ⭐⭐
MANDATORY 必须在事务中,否则报错 ⭐
NEVER 禁止事务,否则报错 ⭐

事务传播行为决定了多个事务方法调用时的事务边界。REQUIRED 是默认值,适合大多数业务场景。REQUIRES_NEW 用于需要独立提交的操作,如日志记录。NESTED 用于需要部分回滚的场景。理解它们的区别,才能在设计事务时做出正确的选择。

相关推荐
海宇服务1 小时前
零信任架构实战:基于海宇车辆vin码查车辆信息详版构建自动化汽配云仓参配核验网关
java·人工智能·架构·自动化
wno7041 小时前
Spring Boot整合Flyway
java·spring boot·后端
vx_Biye_Design1 小时前
springboot旅游管理系统18006-计算机课程设计、毕业设计
java·spring boot·后端·python·elasticsearch·django·课程设计
Wang's Blog1 小时前
Java 中间件之 RabbitMQ 快速入门: 简单队列模型快速入门
java·中间件·java-rabbitmq
anxiao_m1 小时前
跨地域大文件怎么传?2026主流传输软件实测对比
大数据·数据库·文件传输
Su米苏2 小时前
基于 Token 预算的上下文压缩控制器(Context Compaction Controller)
前端·数据库·人工智能
骇客野人2 小时前
Java 开发组件大全覆盖后端主流技术栈(基础、Web、ORM、中间件、微服务、安全、工具、测试、运维、信创适配常用组件)
java·前端·中间件
暖核2 小时前
Redis 从基础到集群实战:数据类型、客户端、高可用架构完整梳理
数据库·redis·架构
2401_888859712 小时前
STM32H733 MPU、AXI、FMC学习
java·开发语言·stm32·spring