这两天在搞事务相关的东西,真的是越研究越觉得 Spring 的那套事务钩子(Transaction Synchronization)简直“真香”。之前还真没怎么注意过这玩意儿,直到遇到一个小坑才发现,它其实能解决不少过去那种“事务之后要干点啥”的问题。
场景背景
我当时写的是一个业务模块,用户下单之后需要异步发个消息到 MQ,用于通知库存系统。这个逻辑很常见吧? 最开始我是这么写的:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
mqService.sendOrderCreated(order.getId()); // 发送 MQ
}
结果问题就来了——有一次数据库事务还没提交,消息就先发出去了,消费者那边收到消息再去查订单,发现根本查不到,日志一堆order not found。 后来一查才知道,这是因为 Spring 的事务提交是延迟到方法执行完之后的,但 MQ 那个调用是在事务还没提交前就发了。
这时候要么你用事务消息(比如 RocketMQ 那种半消息机制),要么你就得想办法在事务真正提交后再发 MQ。
TransactionSynchronization 真正的宝藏
Spring 其实早就给你留了后门: 它有个叫TransactionSynchronizationManager的工具类,可以注册一个“钩子”,在事务提交之后自动回调。 简单理解,就是你可以“挂”一个监听器,事务完成后自动执行。
代码长这样:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
mqService.sendOrderCreated(order.getId());
}
});
}
或者用新版的写法(Spring 5 之后):
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
mqService.sendOrderCreated(order.getId());
}
});
这下完美解决问题:只有事务真正提交成功了,才会去发 MQ。 如果事务回滚了,这个钩子不会被触发。
那如果我需要“事务完成后做点清理”呢?
也有afterCompletion(int status)这个回调方法,不管事务成功还是失败都会触发。 比如要清除一些缓存、关闭连接、重置状态这些事,就可以写在里面:
@Override
public void afterCompletion(int status) {
if (status == STATUS_COMMITTED) {
log.info("事务提交完成");
} else if (status == STATUS_ROLLED_BACK) {
log.warn("事务已回滚,执行补偿逻辑");
}
}
Spring 的TransactionSynchronization一共提供了好几个时机点,比如:
beforeCommit():在提交事务前执行;
beforeCompletion():事务完成前,无论成败;
afterCommit():事务提交成功后;
afterCompletion():事务完成(成功或失败)后;
suspend()/resume():事务挂起或恢复时。
这些钩子如果用得好,真的是神器。
一个真实的使用场景
那天我们组的老王写了个接口,业务逻辑是用户下单后要生成一个支付二维码。二维码的生成依赖订单 ID。 老王的写法是:保存订单 立刻生成二维码。 然后用户时不时就反馈“二维码失效”,查日志才发现二维码生成时事务还没提交,支付系统查不到订单数据。
后来改成这样:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
qrCodeService.generate(order.getId());
}
});
}
从此世界清净,问题彻底解决。 有时候真的是小细节决定成败。
和 @TransactionalEventListener 的区别
有朋友可能会问:Spring 不是还有个@TransactionalEventListener吗? 确实,那也是干同样事情的,只不过更优雅一些。
比如:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
applicationEventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
}
@Component
public class OrderCreatedListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderCreatedEvent event) {
mqService.sendOrderCreated(event.getOrderId());
}
}
这样写就更清爽了,而且还能解耦业务逻辑。 区别在于:
TransactionSynchronization更底层,灵活度更高;
@TransactionalEventListener更语义化,推荐在事件驱动架构中用;
前者可以精细控制执行顺序,后者更偏向 Spring 事件模型。
注意事项
不过坑也不是没有。 比如如果当前线程没有开启事务,TransactionSynchronizationManager.registerSynchronization()会直接抛异常:
“Transaction synchronization is not active”。
所以使用前最好判断一下:
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(...);
} else {
mqService.sendOrderCreated(order.getId());
}
这样即使没有事务,也能兜底。
总结一句话: 以前我们老想着用“事务消息”“本地消息表”“补偿机制”,现在很多小场景,其实一个事务钩子函数就能轻松搞定。 尤其是那种事务提交后要发消息、清缓存、刷日志、调用外部接口的情况。
用得好,它能让代码既安全又优雅。
说真的,Spring 这套设计真是“越用越香”。以前我嫌它啰嗦,现在只想说一句:TransactionSynchronization,真香!