Spring的事务捕捉器一看就懂!

4 阅读4分钟

1. 事务回调是什么?

在 Spring 中,事务回调(Transaction Callback)指的是:在事务的不同阶段(开始、提交前、提交后、回滚后、完成后等)由事务框架触发的“钩子”,允许业务代码在事务边界内/外执行一些额外逻辑。

常见用途:

  • 事务成功提交后再发送 MQ、发通知、刷新缓存,避免“发出去了但事务回滚”的不一致。
  • 在**事务完成后(提交或回滚)**做资源清理。
  • 事务挂起/恢复时做上下文处理(较少见)。

2. Spring 事务回调的核心入口:TransactionSynchronization

Spring 提供了 TransactionSynchronization 体系来表达这些回调点。你可以通过:

  • TransactionSynchronizationManager.registerSynchronization(...)
  • 或更常用的适配方法 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {...})(新版本更倾向直接实现接口)

来注册一个同步器。

常见回调方法(按时机理解)

  • beforeCommit(boolean readOnly):提交前(仍在事务内)。适合校验、写入最后的审计字段等。
  • beforeCompletion():事务完成前(提交或回滚都会走)。
  • afterCommit()事务已提交成功后(最常用)。适合发消息、调用外部系统。
  • afterCompletion(int status):事务完成后(无论提交或回滚)。适合清理 ThreadLocal、释放资源。
  • suspend() / resume():事务挂起与恢复(例如传播行为导致外层事务挂起)。

3. 如何在业务代码里使用(推荐模式:afterCommit)

当你需要“只有事务提交后才执行”的逻辑,可以在事务方法里注册:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
	@Override
	public void afterCommit() {
		// 只在提交成功后执行
		publishEventOrSendMq();
	}

	@Override
	public void afterCompletion(int status) {
		// 提交或回滚都会执行
		cleanup();
	}
});

要点:

  • 必须在事务已开启且当前线程绑定了事务时注册,否则不会生效。
  • afterCommit() 发生在提交之后,因此不要再依赖同一个事务里的连接状态。

4. 与 TransactionTemplate / @Transactional 的关系

  • @Transactional:声明式事务,Spring 在方法调用前后自动开启、提交/回滚事务;你在方法体内注册回调即可。
  • TransactionTemplate.execute(...):编程式事务,回调本身就是“事务内执行”的代码块,但你仍可以在块内注册 TransactionSynchronization 来拿到 afterCommit 等阶段。

5. 常见坑与最佳实践

  1. 不要在事务未提交前对外发布不可撤销动作

    • 例如发 MQ、HTTP 调外部系统。
    • afterCommit() 来保证一致性。
  2. 异步执行要注意线程切换

    • 事务同步器回调在同一线程触发。
    • 若要异步,建议在 afterCommit() 中再提交到线程池。
  3. 回调里抛异常的影响

    • 事务提交阶段抛异常可能影响提交流程。
    • afterCommit() 抛异常通常不会让事务“回滚”,但会影响调用方感知(取决于调用栈)。回调内建议自行 try/catch 并记录。
  4. 同一事务可注册多个同步器

    • 会按注册顺序执行(具体顺序以 Spring 实现为准,通常是按顺序/优先级)。

6. 何时用事务回调,何时用事件?

  • 事务回调:你需要明确的事务阶段(尤其是 afterCommit)。

  • 事务事件(TransactionalEventListener) :你想用“事件模型”解耦发布与处理,同时仍需要 afterCommit 语义。

    • 典型:@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

如果你愿意,我可以把这一页再补充一段:@TransactionalEventListener 的示例与和 ApplicationEventPublisher 的组合写法。


7. @TransactionalEventListener + ApplicationEventPublisher 组合示例

7.1 定义事件(领域事件)

public record OrderPaidEvent(Long orderId) {}

7.2 在事务内发布事件(用 ApplicationEventPublisher

@Service
public class OrderService {
	private final ApplicationEventPublisher publisher;
	private final OrderRepository orderRepository;

	public OrderService(ApplicationEventPublisher publisher, OrderRepository orderRepository) {
		this.publisher = publisher;
		this.orderRepository = orderRepository;
	}

	@Transactional
	public void pay(Long orderId) {
		// 1) 事务内更新数据库
		orderRepository.markPaid(orderId);

		// 2) 发布事件:注意这里仍处于事务内
		publisher.publishEvent(new OrderPaidEvent(orderId));
	}
}

要点:

  • publishEvent(...) 发生在事务内,但监听器可以选择在 AFTER_COMMIT 才执行,从而避免“事件处理成功但事务回滚”的不一致。

7.3 在提交后处理事件(TransactionPhase.AFTER_COMMIT

@Component
public class OrderPaidListener {

	@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
	public void onOrderPaid(OrderPaidEvent event) {
		// 这里已经提交成功:适合发 MQ、通知外部系统、刷新缓存等
		sendMq(event.orderId());
	}
}

7.4 其他阶段与常用补充

  • phase = TransactionPhase.BEFORE_COMMIT:提交前(仍在事务内),适合校验或补写审计数据。

  • phase = TransactionPhase.AFTER_ROLLBACK:回滚后,适合补偿或记录失败原因。

  • phase = TransactionPhase.AFTER_COMPLETION:完成后(提交或回滚都会走),适合清理资源。

  • fallbackExecution = true:当没有事务时也执行监听器(默认 false)。

    • 只有在你能接受“非事务场景也触发副作用”时才打开。

7.5 异步监听(提交后再异步)

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderPaidAsync(OrderPaidEvent event) {
	sendMq(event.orderId());
}

要点:

  • 需要启用 @EnableAsync 并配置线程池。
  • @Async 会切换线程,因此不要依赖任何 ThreadLocal 上下文,必要时把信息放进事件对象里。