1. 场景与要解决的问题
在业务代码里,常见诉求是:只有当数据库事务真正提交成功后,才去执行某些“后置动作”,例如:
发送 MQ、推送消息、写审计/埋点日志、刷新缓存、通知外部系统等。
如果这些动作在事务提交前就执行,一旦事务最后回滚,就会出现数据与副作用不一致(例如:数据库没落库,但 MQ 已发出)。
Spring 给出两类工具来“跟随事务走”:
TransactionSynchronization
:直接注册事务回调,在afterCommit()
等时点执行。@TransactionalEventListener
:发布事件 + 事务阶段监听,在AFTER_COMMIT
等阶段触发监听方法。
2. TransactionSynchronization
使用方法
2.1 它是什么
事务同步回调接口(Transaction Synchronization Callback Interface)。
它能让你在事务的关键节点(提交前、提交后、回滚后、完成后)挂接同步逻辑。
本质是 “钩子/回调”,属于 Spring 事务 SPI(Service Provider Interface)扩展点。
2.2 使用方法
@Service
public class WithdrawService {@Transactional(rollbackFor = Exception.class)public void createWithdrawOrder(Long userId, BigDecimal amount) {// 1) 业务数据更新(示例)// - 扣可用余额、加冻结余额、插入订单等// - 此处略…Long orderId = 123L; // 假设是插入订单后拿到的IDBigDecimal income = amount;// 2) 绑定到“当前事务”的提交后回调TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {try {// 3) 事务真正提交成功后才会执行到这里withdrawProducer.sendMessage(orderId);log.info("已发送提现MQ, orderId={}", orderId);} catch (Exception ex) {// 注意:此时事务已提交,失败不会回滚主事务log.error("提交后发送MQ失败, orderId={}", orderId, ex);// 可在此触发重试/告警/记录Outbox补偿等}}});}
}
2.3 最佳实践
必须在活动事务中注册:可用
TransactionSynchronizationManager.isSynchronizationActive()
检查。不要在
afterCommit
里做重 IO/耗时操作,以免拉长请求尾延迟;建议再丢到自定义线程池执行。异常处理:
afterCommit
里异常不会回滚主事务(已提交),要自处置(告警/重试/Outbox)。事务传播:在哪一层注册,就跟随那一层的事务。若内部有
REQUIRES_NEW
子事务,你在子事务里注册的回调只跟随子事务。多层回调:如果需要控制多个回调的顺序,可让回调实现
org.springframework.core.Ordered
接口,getOrder()
返回值越小优先级越高。
3. @TransactionalEventListener
使用方法
3.1 它是什么
发布-订阅风格的事务阶段监听。业务方法里发布事件,监听方法用
@TransactionalEventListener
声明在指定事务阶段运行(常用AFTER_COMMIT
)。事件可以被多个监听器消费;可配
@Async
在提交后异步执行。
3.2 使用方法(同步监听模板)
// 1) 自定义事件(POJO 即可)
public record WithdrawOrderCreatedEvent(Long orderId, Long userId, BigDecimal amount) {}// 2) 事务中发布事件
@Service
public class WithdrawService {@Autowired private ApplicationEventPublisher publisher;@Transactional(rollbackFor = Exception.class)public void createWithdrawOrder(Long userId, BigDecimal amount) {// 业务入库…(略)Long orderId = 123L;publisher.publishEvent(new WithdrawOrderCreatedEvent(orderId, userId, amount));}
}// 3) 监听端:仅在“提交成功后”触发
@Component
public class WithdrawEventListener {@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void onCreated(WithdrawOrderCreatedEvent evt) {try {withdrawProducer.sendMessage(evt.orderId());log.info("提交后发送MQ成功, orderId={}", evt.orderId());} catch (Exception ex) {log.error("提交后发送MQ失败, orderId={}", evt.orderId(), ex);}}
}
3.3 提交后异步执行(可选)
@Configuration
@EnableAsync
public class AsyncCfg implements AsyncConfigurer {@Override public Executor getAsyncExecutor() {return Executors.newFixedThreadPool(8);}
}@Component
public class WithdrawAsyncListener {@Async@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void onCreated(WithdrawOrderCreatedEvent evt) {withdrawProducer.sendMessage(evt.orderId()); // 提交后异步发送}
}
4. 对比与选型
一致性保障
二者都能保证:只有事务提交成功后才执行(
afterCommit
/AFTER_COMMIT
)。
耦合与扩展
TransactionSynchronization
:代码直接注册回调,与业务方法耦合,适合单一后置动作。@TransactionalEventListener
:发布-订阅,天然解耦,一个事件可被多个监听器消费,易扩展。
异步能力
TransactionSynchronization
:天生同步;可在回调内手动丢线程池异步。@TransactionalEventListener
:可直接叠加@Async
在提交后异步执行。
无事务时的行为
TransactionSynchronization
:必须存在活动事务,否则注册失败。@TransactionalEventListener
:默认无事务不触发;fallbackExecution = true
可强制执行(会失去“提交后”语义)。
性能与代码复杂度
TransactionSynchronization
:路径最短、开销最小、代码最少。@TransactionalEventListener
:有事件派发的轻微开销,换来更好的解耦与可维护性。
测试与团队协作
TransactionSynchronization
:更贴近底层钩子,单一动作简单直观。@TransactionalEventListener
:语义清晰(业务事件),多人协作与模块解耦更友好,监听可独立单测。
推荐选型(面向常见场景)
只需一个消费方,追求超轻量 → 选
TransactionSynchronization.afterCommit()
。需要解耦/多个消费方/可异步 → 选
@TransactionalEventListener(phase = AFTER_COMMIT)
。需要强可靠(不能丢消息) → 在以上任一方案外,叠加 Outbox 模式(业务表 + 出站表同事务写入,后台可靠投递 MQ/补偿重试)。