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. 常见坑与最佳实践
-
不要在事务未提交前对外发布不可撤销动作
- 例如发 MQ、HTTP 调外部系统。
- 用
afterCommit()来保证一致性。
-
异步执行要注意线程切换
- 事务同步器回调在同一线程触发。
- 若要异步,建议在
afterCommit()中再提交到线程池。
-
回调里抛异常的影响
- 事务提交阶段抛异常可能影响提交流程。
afterCommit()抛异常通常不会让事务“回滚”,但会影响调用方感知(取决于调用栈)。回调内建议自行 try/catch 并记录。
-
同一事务可注册多个同步器
- 会按注册顺序执行(具体顺序以 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 上下文,必要时把信息放进事件对象里。