跳到主要内容

10.5 传播行为

当多个事务方法在同一线程中相互调用时,传播行为决定了事务如何在这些方法间传播。

传播行为可以用于三种事务入口:

注解式事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(long orderId) {
...
}
模板事务
txTemplate.execute(tranStatus -> {
...
return null;
}, Propagation.NESTED);
编程式事务
TransactionStatus tran = txManager.begin(Propagation.REQUIRED);

如何选择​

想达到的效果推荐传播行为
大多数业务方法:有事务就加入,没有就新建REQUIRED
审计日志、操作记录:希望独立提交REQUIRES_NEW
外层事务中某一步可以失败,但不影响外层继续NESTED
有事务就跟随,没有事务也能运行SUPPORTS
一段代码必须绕开事务执行NOT_SUPPORTED
明确禁止在事务中运行NEVER
必须由外层事务包住,否则就是调用错误MANDATORY

加入已有事务 (REQUIRED)​

尝试加入已经存在的事务中,如果没有则开启一个新的事务。

  • 常量 Propagation.REQUIRED
订单创建默认使用 REQUIRED
@Transactional
public void createOrder(long orderId) {
orderMapper.insertOrder(orderId);
orderMapper.insertOrderItems(orderId);
}

REQUIRED 是默认值。外层没有事务时,它创建事务;外层已经有事务时,它加入外层事务。内层方法正常返回并不代表立即提交,最终提交或回滚由最外层事务决定。dbVisitor 本地管理器的内层 REQUIRED 回滚不会自动将外层标记为 rollback-only;如果外层捕获并吞掉内层异常,仍可能提交这些写入。需要整体失败时,应让异常继续传播或显式标记外层回滚。

时序事务 A事务 B效果
T1begin开启事务 A
T2insert data1
T3begin加入事务 A(不做操作)
T4insert data2
T5commit/rollback不做操作(由外层决定)
T6insert data3
T7commit/rollback提交/回滚事务 A

独立事务 (REQUIRES_NEW)​

挂起当前已存在的事务(如果有),开启一个全新的独立事务,新事务与旧事务彼此无关。

  • 常量 Propagation.REQUIRES_NEW
外层失败时,审计日志仍可独立提交
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(long orderId, String action) throws java.sql.SQLException {
jdbcTemplate.executeUpdate(
"insert into order_audit(order_id, action) values(?, ?)",
new Object[] { orderId, action }
);
}
信息
  • 挂起会导致当前线程绑定的 Connection 暂时不可用。
  • 挂起后事务管理器会创建一个新的 Connection 用作当前线程的数据库连接。
时序事务 A事务 B效果
T1begin开启事务 A
T2insert data1
T3begin挂起 A → 新建 Connection B → 开启事务 B
T4insert data2
T5commit/rollback提交/回滚 B → 恢复 A
T6insert data3
T7commit/rollback提交/回滚事务 A

嵌套事务 (NESTED)​

在当前事务中通过 Savepoint 方式开启一个子事务。子事务回滚不影响外层事务,但外层事务回滚会连带子事务。

  • 常量 Propagation.NESTED
优惠券失败时,只回滚优惠券部分
txTemplate.execute(tranStatus -> {
couponMapper.bindCoupon(orderId, couponId);
return null;
}, Propagation.NESTED);

NESTED 依赖数据库和驱动对 Savepoint 的支持。它适合“一个大事务中的局部步骤允许失败”的场景。

时序事务 A事务 B效果
T1begin开启事务 A
T2insert data1
T3begin创建 Savepoint B
T4insert data2
T5commit/rollback释放/回滚 Savepoint B
T6insert data3
T7commit/rollback提交/回滚事务 A

跟随环境 (SUPPORTS)​

如果当前没有事务,以非事务方式执行;如果有事务,则加入当前事务(效果等同 REQUIRED)。

  • 常量 Propagation.SUPPORTS
查询方法可以跟随调用方环境
@Transactional(propagation = Propagation.SUPPORTS)
public OrderInfo queryOrder(long orderId) {
return orderMapper.queryOrder(orderId);
}
信息

SUPPORTS 的本质是不作为 — 不会主动开启也不会阻止事务。

非事务方式 (NOT_SUPPORTED)​

如果当前没有事务,以非事务方式执行;如果有事务,则将当前事务挂起后以非事务方式执行。

  • 常量 Propagation.NOT_SUPPORTED
大查询不占用外层事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public List<OrderReport> queryReport() {
return reportMapper.queryOrderReport();
}
时序事务 A事务 B效果
T1begin开启事务 A
T2insert data1
T3begin挂起事务 A
T4insert data2非事务方式执行
T5commit/rollback恢复事务 A
T6insert data3
T7commit/rollback提交/回滚事务 A

排除事务 (NEVER)​

如果当前没有事务,以非事务方式执行;如果有事务,直接抛出异常。

  • 常量 Propagation.NEVER
禁止事务内调用
@Transactional(propagation = Propagation.NEVER)
public void rebuildSearchIndex() {
...
}

要求事务 (MANDATORY)​

如果当前有事务,加入当前事务;如果没有事务,直接抛出异常。

  • 常量 Propagation.MANDATORY
必须由外层事务保护
@Transactional(propagation = Propagation.MANDATORY)
public void insertOrderItem(long orderId, long skuId) {
orderMapper.insertOrderItem(orderId, skuId);
}

传播行为对比​

传播行为无事务时有事务时
REQUIRED新建事务加入已有事务
REQUIRES_NEW新建事务挂起已有 → 新建事务
NESTED新建事务Savepoint 子事务
SUPPORTS非事务执行加入已有事务
NOT_SUPPORTED非事务执行挂起已有 → 非事务执行
NEVER非事务执行抛出异常
MANDATORY抛出异常加入已有事务

深入阅读​

  • 注解式事务:在 @Transactional 上指定传播行为。
  • 模板事务:在 execute 的第二个参数中指定传播行为。
  • 跨 API 事务:了解不同 API 调用如何复用或切换事务连接。