首页
学习
活动
专区
圈层
工具
发布

SpringBoot + 事务钩子函数,真香!

这两天在搞事务相关的东西,真的是越研究越觉得 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,真香!

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OJ_YzCpzSfYJ92Eqpcy-fifg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券