必须用 aftercommit() 而非 aftercompletion() 实现事务提交后可靠执行,因前者确保数据库已落盘且资源未释放,后者时机不可控且不保证顺序;注册前须校验事务活跃状态,异步场景需通过信号触发独立事务。

事务提交后执行逻辑,必须用 afterCommit() 而不是 afterCompletion()
很多人误以为 afterCompletion(int status) 就是“事务结束时”,但它的触发时机是事务**完成之后**(包括提交成功、回滚成功、甚至异常中断),且参数 status 仅反映最终状态,无法区分“是否真正持久化成功”。而金融、订单、消息投递等场景要求的是“数据库已落盘、不可逆”这个确切时刻——只有 afterCommit() 能保证这点。
关键区别:
-
afterCommit():仅在 事务真正提交成功、JDBC Connection 已 flush 到数据库、事务资源尚未释放 时调用;此时数据已满足 ACID 的 Durability -
afterCompletion(TransactionSynchronization.STATUS_COMMITTED):可能在事务管理器内部清理阶段才回调,此时连接可能已 close,甚至部分资源已被回收 - 若注册了多个同步器,
afterCommit()的执行顺序与注册顺序一致;afterCompletion()不保证顺序
注册前必须确认当前线程处于活跃事务中,否则抛 IllegalStateException
TransactionSynchronizationManager.registerSynchronization() 不是“随时可调”的工具方法。它只在事务上下文已激活(即 isActualTransactionActive() == true)时才接受注册。常见踩坑点:
- 在非
@Transactional方法里直接调用,或在事务被Propagation.NOT_SUPPORTED暂停的子方法中调用 → 报IllegalStateException: Transaction synchronization is not active - 异步线程(如
@Async或手动 new Thread)中调用 → 因ThreadLocal隔离,事务上下文为空 - 事务方法内嵌套调用另一个无事务的方法,再从该方法里注册 → 上下文已丢失
安全写法是显式检查:
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 可靠执行
}
});
} else {
// 降级处理:记录告警 or 直接触发补偿逻辑
}
事务提交后发送 MQ 失败,不能让主事务回滚,但必须有兜底
这是最典型的使用场景,也是最容易出问题的地方:afterCommit() 中抛异常,不会影响主事务结果——因为数据库早已提交。但若 MQ 发送失败又没处理,就导致“订单已创建,但下游没收到”,数据不一致。
必须做三件事:
- 捕获所有异常,禁止向上抛出(否则会触发日志框架误报或容器线程中断)
- 记录明确错误日志,含
orderId、timestamp、exception message - 立即触发补偿机制:比如写入本地
outbox表、投递延迟重试消息、或调用幂等重试服务
示例片段:
@Override
public void afterCommit() {
try {
mqProducer.send(buildOrderEvent(orderId));
} catch (Exception e) {
log.error("MQ send failed for orderId={}, will retry via outbox", orderId, e);
outboxRepository.insert(new OutboxRecord(orderId, "ORDER_CREATED", System.currentTimeMillis()));
}
}
别依赖 TransactionSynchronizationManager 做跨线程事务传递
有人试图把 TransactionSynchronization 对象序列化后传给新线程,期望在另一线程里“继续事务生命周期”——这完全不可行。因为 TransactionSynchronizationManager 内部所有状态(resources、synchronizations、currentTransactionName 等)都基于 ThreadLocal,新线程拿不到任何父线程的事务上下文。
如果业务真需要“事务后触发异步任务”,正确做法是:
- 在
afterCommit()中写入轻量级触发信号(如 Redis key、DB flag、Kafka event) - 由独立消费者监听该信号,启动新事务执行后续逻辑
- 避免在同步回调里做耗时操作(如 HTTP 调用、文件 IO),否则拖慢主事务响应
事务同步器本质是单线程、短生命周期、强上下文绑定的钩子,不是分布式协调器。










