当前位置: 首页 > news >正文

Spring 事务提交成功后执行额外逻辑

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 它是什么

  1. 发布-订阅风格的事务阶段监听。业务方法里发布事件,监听方法用 @TransactionalEventListener 声明在指定事务阶段运行(常用 AFTER_COMMIT)。

  2. 事件可以被多个监听器消费;可配 @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/补偿重试)。


文章转载自:

http://zfklswDY.gtdnq.cn
http://9sgX74xZ.gtdnq.cn
http://ym5cw7TI.gtdnq.cn
http://WlX0TyM5.gtdnq.cn
http://C5RW4eSB.gtdnq.cn
http://4FA542OA.gtdnq.cn
http://MELzRvu1.gtdnq.cn
http://fvv7q6YJ.gtdnq.cn
http://SUqcY0WJ.gtdnq.cn
http://mmFx2I9P.gtdnq.cn
http://2pndm5tp.gtdnq.cn
http://FyRHmp2J.gtdnq.cn
http://ACqvpsfq.gtdnq.cn
http://Tu5AjA8N.gtdnq.cn
http://SIhRI7sW.gtdnq.cn
http://f4D7aT4A.gtdnq.cn
http://cV7Y3osV.gtdnq.cn
http://0OpPT9m8.gtdnq.cn
http://Eut5oOA1.gtdnq.cn
http://hwVVoRLc.gtdnq.cn
http://vr3ACAgm.gtdnq.cn
http://Q5IMLbUx.gtdnq.cn
http://VZG4CPPg.gtdnq.cn
http://m3Ff4JUT.gtdnq.cn
http://X4QoobOB.gtdnq.cn
http://2PCKi5NM.gtdnq.cn
http://KicSSuSL.gtdnq.cn
http://hb4iyXAc.gtdnq.cn
http://4hRuBQrH.gtdnq.cn
http://voXWiJkw.gtdnq.cn
http://www.dtcms.com/a/366175.html

相关文章:

  • Attention-Based Map Encoding for Learning Generalized Legged Locomotion
  • MMD动画(二)动作制作
  • Hoppscotch:开源轻量API测试工具,秒启动高效解决临时接口测试需求
  • 【机器学习】HanLP+Weka+Java算法模型
  • 算法随笔(一)
  • Electron 执行python脚本
  • Dubbo(分布式RPC调用和分布式文件储存)
  • 如何简单理解状态机、流程图和时序图
  • 成为一个年薪30W+的FPGA工程师是一种什么体验?
  • 进程与线程详解, IPC通信与RPC通信对比,Linux前台与后台作业
  • 在国企干了 5 年 Java,居然不知道 RPC?这正常吗?
  • VU9P板卡设计方案:基于VU9P的32@ SFP28+4@ QSFP28路光纤交换板卡
  • Zynq开发实践(FPGA之uart发送)
  • 如何在 IntelliJ IDEA 中进行全局替换某个字段(或文本)
  • 案例精述 | 防护即智能 Fortinet赋能英科全栈安全重构实践
  • React学习之路永无止境:下一步,去向何方?
  • C#上位机解决ComboBox下拉框加载卡顿问题探析
  • wpf中资源的使用
  • 【Ubuntu扩容】Ubuntu启动项丢失、增加硬盘相关操作记录贴
  • Dubbo分布式服务框架全解析
  • 十一、容器化 vs 虚拟化-K8s-Kustomize
  • 免费低代码谁更优?斑斑与氚云深度对比,中小企业数字化转型选对平台很关键
  • 热烈庆祝“中国抗战胜利80周年”,织信低代码助力国之重器砥砺前行!
  • vue+elementUI 进行表格行内新增及校验,同行其他输入框数据影响当前输入框校验结果
  • Web与Nginx网站服务
  • VUE中引入tailwindcss样式(用于GemDesgin的页面生成)
  • 英飞凌ASIL-D级无刷电机驱动芯片TLE9189守护汽车安全
  • 新手SEO高效入门实践指南
  • Linux 基础IO-从 “一切皆文件” 到自定义 libc 缓冲区
  • 字符串(1)