事务的传播行为

总结

详细介绍了 7 种传播行为并举例说明。

概念

什么是事务的传播行为?

事务传播行为 (Propagation Behavior) 定义了事务方法相互调用时,事务应该如何传播。Spring 提供了 7 种传播行为,通过 @Transactional(propagation = Propagation.XXX) 来指定。

详情

7 种传播行为表格对比

传播行为 说明 是否加入已有事务? 是否新建事务? 是否挂起当前事务? 是否允许无事务执行? 是否支持嵌套回滚? 常见用途
REQUIRED (默认) 0 如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务。 ✅ 是 ❌ 否(如果存在则加入) ❌ 否 ❌ 否 ❌ 否 最常用,适用于大多数业务操作
SUPPORTS 1 如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式继续运行。 ⚠️ 可选 ❌ 否 ❌ 否 ✅ 是 ❌ 否 查询操作,可有可无事务
MANDATORY 2 如果当前存在事务,则加入该事务;如果没有事务,则抛出异常。 ✅ 必须加入 ❌ 否 ❌ 否 ❌ 否 ❌ 否 强制要求调用方提供事务
REQUIRES_NEW 3 总是创建一个新的事务,并暂停当前事务(如果有)。 ❌ 否 ✅ 是 ✅ 是 ❌ 否 ❌ 否 独立事务,如日志、审计、异步处理
NOT_SUPPORTED 4 以非事务方式执行操作,并暂停当前事务(如果有)。 ❌ 否 ❌ 否 ✅ 是 ✅ 是 ❌ 否 非事务执行,适合统计、报表等
NEVER 5 以非事务方式执行操作;如果当前存在事务,则抛出异常。 ❌ 否 ❌ 否 ❌ 否 ✅ 是(但不能有事务) ❌ 否 明确禁止事务环境
NESTED 6 如果当前存在事务,则在嵌套事务内执行;如果没有事务,则创建一个新事务(与 REQUIRED 相同)。 ✅ 是(在现有事务中嵌套) ❌ 否(复用外层事务) ❌ 否 ❌ 否 ✅ 是 支持局部回滚,如转账中的分阶段操作
  • 说明:
列名 说明
是否加入已有事务? 如果当前已经有事务,该方法是否会加入?
是否新建事务? 无论当前是否有事务,该方法是否都会新建一个事务?
是否挂起当前事务? 在开启新事务之前,是否会挂起当前事务?
是否允许无事务执行? 如果当前没有事务,该方法能否继续执行?
是否支持嵌套回滚? 是否能在当前事务中创建保存点,并实现局部回滚?
常见用途 这种传播行为最适合用于什么场景?

传播行为具体示例

基础型(默认 & 兼容)

REQUIRED(默认)

场景:用户注册时,需要同时保存用户信息和创建初始账户。

@Transactional
public void registerUser(User user) {
    // 保存用户信息(事务开始)
    userRepository.save(user);
    
    // 创建初始账户(加入当前事务)
    accountService.createInitialAccount(user.getId());
}

说明:registerUser()createInitialAccount() 在同一个事务中,确保数据一致性。如果任一步骤失败,整个事务回滚。

SUPPORTS(兼容性好)

场景:查询用户信息时,根据调用方是否有事务决定是否在事务中执行。

@Transactional(propagation = Propagation.SUPPORTS)
public User findUserById(Long id) {
    // 查询用户信息
    return userRepository.findById(id);
}

说明:如果调用方有事务,findUserById() 加入该事务;如果没有事务,则以非事务方式执行,适用于查询操作。

强制型(确保事务上下文)

MANDATORY

场景:更新账户余额时,必须在事务中进行,防止数据不一致。

@Transactional(propagation = Propagation.MANDATORY)
public void updateAccountBalance(Long accountId, BigDecimal amount) {
    // 更新账户余额
    accountRepository.updateBalance(accountId, amount);
}

说明:如果调用方没有事务,将抛出 IllegalTransactionStateException,确保方法在事务中执行。

NEVER

场景:验证数据合法性时,必须在非事务环境下执行。

@Transactional(propagation = Propagation.NEVER)
public void validateData(Data data) {
    // 验证数据合法性
    if (data == null) {
        throw new ValidationException("Data cannot be null");
    }
}

说明:如果调用方有事务,将抛出 IllegalTransactionStateException,确保方法不在事务中执行。

隔离型(事务解耦)

REQUIRES_NEW

场景:订单处理时,需要记录操作日志,无论订单处理是否成功,日志都必须保存。

@Transactional
public void processOrder(Order order) {
    // 处理订单(外层事务)
    // ...

    // 记录操作日志(新事务)
    logService.logOrderOperation(order);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderOperation(Order order) {
    // 保存日志,独立于外层事务
    logRepository.save(new Log(order));
}

说明:logOrderOperation() 使用 REQUIRES_NEW,确保日志记录不受订单处理事务的影响,独立提交。外部方法 processOrder() 异常回滚,自身事务回滚,不影响 logOrderOperation() 方法,内部事务已经提交。logOrderOperation() 方法异常,自身回滚,不影响 processOrder() 外部方法。

NOT_SUPPORTED

场景:生成报表时,不需要事务支持,避免长时间运行影响事务性能。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void generateReport() {
    // 生成报表,非事务执行
    // ...
}

说明:即使调用方有事务,generateReport() 也会以非事务方式执行,提高性能。

嵌套型(局部回滚)

NESTED

场景:银行间转账操作时,需要部分回滚,例如转出成功但转入失败。目的是允许局部失败而不影响整体事务。追求的是最终一致性,通常会有补偿机制进行兜底。

@Transactional
public void transferFunds(Long fromAccount, Long toAccount, BigDecimal amount) {
    // 转出资金(外层事务)
    accountService.debit(fromAccount, amount);
    
    try {
        // 转入资金(嵌套事务)
        accountService.credit(toAccount, amount);
    } catch (Exception e) {
        // 转入失败,仅回滚嵌套事务,转出操作仍有效
    }
}

@Transactional(propagation = Propagation.NESTED)
public void credit(Long accountId, BigDecimal amount) {
    // 转入资金
    accountRepository.addBalance(accountId, amount);
}

@Transactional(propagation = Propagation.NESTED)
public void debit(Long accountId, BigDecimal amount) {
    // 转出资金
    accountRepository.reduceBalance(accountId, amount);
}

说明:credit()debit() 使用 NESTED,可以在外层事务中创建嵌套事务,实现部分回滚。credit() 将在现有事务中创建保存点,而不是新建全新事务。credit() 出错可以单独回滚到保存点,不影响外部事务,如果外部事务回滚,则整个事务回滚,包括嵌套 credit() 部分。

使用建议

根据具体的业务需求选择合适的传播行为。比如:

  • 需要一个独立事务,不受外部影响 → 用 REQUIRES_NEW
  • 不想参与事务,也不想影响别人 → 用 NOT_SUPPORTED
  • 必须在事务中运行,否则报错 → 用 MANDATORY
  • 部分回滚某个 → 用 NESTED

注意事项

  1. NESTED 传播行为需要 JDBC 3.0 以上驱动和保存点支持
  2. REQUIRES_NEW 会创建新连接,可能增加连接池压力
  3. 传播行为配置要符合业务逻辑,避免过度使用 REQUIRES_NEW
  4. 自调用问题同样会影响传播行为的效果

关联文章


文章作者: huan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 huan !
  目录