Spring 事务事件监听器如何在提交后执行
订单支付、注册成功、库存扣减这类动作经常需要在数据库提交后再发通知、写审计或触发下游处理。直接使用 @EventListener 时,监听器通常会在发布事件的调用线程中立即执行;如果后续事务回滚,外部动作可能已经发生。Spring 提供的 @TransactionalEventListener 可以把监听器绑定到事务阶段,其中默认阶段就是 AFTER_COMMIT。
最小结论:在事务方法中发布事件,在监听方法上声明 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)。如果提交后还要写数据库,把写操作放到另一个 Spring Bean,并使用 REQUIRES_NEW 开启新事务。
官方文档:https://docs.spring.io/spring-framework/reference/data-access/transaction/event.html
- 事件必须在活动事务中发布,否则监听器默认不执行。
AFTER_COMMIT表示原事务已经成功提交,监听异常不能让它回滚。- 提交后继续使用原事务资源写数据,新增变更可能不会再次提交。
- 进程内事件不是可靠消息;跨服务或不能丢的任务应使用 Outbox 等持久化方案。
先实现最小可用的提交后监听
以订单支付为例,用户的可观察目标是:支付状态先可靠入库,只有提交成功后才创建回执。领域事件只携带必要标识,不把仍受持久化上下文管理的实体直接传给监听器。
public record OrderPaidEvent(long orderId) {
// 事件只携带稳定标识,避免监听器依赖延迟加载实体
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public void pay(long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
// 先修改需要由当前事务提交的核心业务状态
order.markPaid();
// 发布动作发生在事务内,监听器会登记事务同步回调
eventPublisher.publishEvent(new OrderPaidEvent(orderId));
}
}
监听器显式写出 AFTER_COMMIT,虽然它是默认值,但代码审查时更容易看出业务意图。
@Component
@RequiredArgsConstructor
public class OrderPaidListener {
private final ReceiptService receiptService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderPaid(OrderPaidEvent event) {
// 只有原事务成功提交后才进入这里
receiptService.createReceipt(event.orderId());
}
}
如果 pay() 在提交前抛出运行时异常,事务回滚,监听器不会执行。它和普通 @EventListener 的核心区别不是“是否使用事件”,而是“执行时机是否绑定事务结果”。
提交后不等于还能继续写原事务
Spring 的 Javadoc 特别提示:在 AFTER_COMMIT、AFTER_ROLLBACK 或 AFTER_COMPLETION 阶段,事务已经结束,但事务资源可能仍然可访问。此时的数据访问看起来还能参与原资源,新增变更却不会再提交。最常见的症状是日志显示监听器执行成功,回执表却没有新增记录。
稳妥做法是把二次落库放在独立 Bean 中,用 REQUIRES_NEW 打开一个真正的新事务:
@Service
@RequiredArgsConstructor
public class ReceiptService {
private final ReceiptRepository receiptRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createReceipt(long orderId) {
// 唯一约束可阻止同一订单重复创建回执
if (receiptRepository.existsByOrderId(orderId)) {
return;
}
receiptRepository.save(Receipt.forOrder(orderId));
}
}
独立 Bean 很重要。Spring 的声明式事务通常依赖代理拦截,若在同一个对象内部用 this.createReceipt() 调用带 REQUIRES_NEW 的方法,调用不会经过代理,新事务可能根本没有创建。

四个事务阶段怎么选
| 阶段 | 执行条件 | 适用动作 | 主要边界 |
|---|---|---|---|
BEFORE_COMMIT | 提交前 | 提交前校验、同事务内补充状态 | 监听异常可影响原事务 |
AFTER_COMMIT | 成功提交后 | 通知、缓存失效、后置处理 | 不能回滚已提交事务 |
AFTER_ROLLBACK | 事务回滚后 | 失败补偿提示、诊断记录 | 补偿动作也要考虑自身失败 |
AFTER_COMPLETION | 提交或回滚完成后 | 统一清理、指标统计 | 需要自行区分最终结果 |
不要把所有事件都改成 AFTER_COMMIT。如果监听动作必须和核心状态一起原子提交,它就不是纯粹的“提交后副作用”,应直接放在原事务里,或使用 BEFORE_COMMIT 并明确失败策略。
监听器没有执行时先查这三项
一、发布事件时没有活动事务
默认情况下,无活动事务时发布的事件会被丢弃,因为 Spring 无法兑现“提交后执行”的语义。虽然可以设置 fallbackExecution = true,但这会让同一监听器在有事务时延后、无事务时立即执行,行为不再一致。除非业务明确接受这两种时序,否则应修复事务入口。
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
fallbackExecution = false
)
public void onOrderPaid(OrderPaidEvent event) {
// 保持严格语义:没有事务就不处理
receiptService.createReceipt(event.orderId());
}
二、@Transactional 因自调用没有生效
例如控制器调用 checkout(),而 checkout() 在同一个服务实例中通过 this.pay() 调用事务方法。内部调用绕过 Spring 代理,pay() 可能没有活动事务,最终表现为监听器不执行。把事务入口提升到外部可代理的方法,或拆到单独服务。
三、测试事务从未真正提交
Spring 测试中的 @Transactional 用例通常在结束时回滚。只调用发布事件的方法并断言监听结果,往往看不到 AFTER_COMMIT。集成测试需要显式提交测试事务,或在非事务测试方法中调用真实事务服务。
异步执行要同时解决线程、事务和失败恢复
AFTER_COMMIT 默认仍在提交线程上执行。监听器耗时过长会延长接口尾部延迟。可以增加 @Async 把后置任务交给线程池,但异步线程没有原线程绑定的事务上下文;如果需要落库,仍应启动新事务。
@Async("businessEventExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendReceipt(OrderPaidEvent event) {
// 异步线程中按事件标识处理,保证重试时可幂等
receiptService.createReceipt(event.orderId());
notificationClient.sendPaidNotice(event.orderId());
}
线程池还要设置容量、队列、拒绝策略和监控。异步监听器抛出的异常不能让已经提交的订单事务回滚,也不会自动获得可靠重试。若通知失败必须恢复,应把任务状态持久化,交给可重试的工作器处理。
进程内监听、异步监听和 Outbox 的选择
事务监听器解决的是“相对于本地事务结果何时执行”,不保证进程不会在提交后、监听前崩溃。若动作允许偶发失败后人工补偿,进程内监听足够轻量;若事件绝不能丢,应在原事务中同时写入 Outbox 表,再由独立发布器投递。
| 方案 | 响应线程 | 故障恢复 | 适用场景 |
|---|---|---|---|
| 同步 AFTER_COMMIT | 提交线程 | 依赖应用补偿 | 短小、可重试或允许降级的后置动作 |
| AFTER_COMMIT + @Async | 业务线程池 | 需自行持久化失败 | 降低响应延迟、允许进程内调度 |
| 事务 Outbox | 独立发布器 | 可查询、可重试 | 跨服务消息、账务链路、不可丢任务 |

用幂等和可观测性处理重复与失败
即使当前实现只触发一次,也应把监听器写成可重入。给事件增加 eventId,在消费记录或业务表上建立唯一约束;执行日志记录事件标识、聚合标识、监听阶段和耗时。这样以后加入重试、消息队列或 Outbox 时,不必重新设计副作用。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(UUID eventId, long orderId) {
// 先用唯一事件标识判断是否已成功处理
if (processedEventRepository.existsById(eventId)) {
return;
}
// 在同一个新事务内完成业务写入和消费标记
receiptRepository.save(Receipt.forOrder(orderId));
processedEventRepository.save(new ProcessedEvent(eventId));
}
不要仅靠内存 Set 去重,应用重启后它会丢失;也不要只依赖“理论上只发布一次”,因为超时重试、人工补偿和消息重投都会制造重复入口。
响应式事务需要传递 Reactor 上下文
Spring Framework 6.1 起,事务事件监听器既能处理 PlatformTransactionManager 管理的线程绑定事务,也能处理 ReactiveTransactionManager 管理的响应式事务。后者把事务信息放在 Reactor Context,而不是 ThreadLocal,因此需要通过 TransactionalEventPublisher 等方式把事务上下文作为事件源传递。不要把命令式示例直接复制到 R2DBC 流程后假设语义完全相同。
提交后监听的集成测试清单
- 成功提交:核心状态已落库,监听副作用随后出现。
- 事务回滚:核心状态与 AFTER_COMMIT 副作用都不存在。
- 无事务发布:在
fallbackExecution=false时不执行。 - 监听器失败:原事务仍保持已提交,失败被记录并可补偿。
- 重复事件:唯一约束或消费记录保证结果不重复。
- 异步饱和:线程池拒绝或超时能够被监控,任务不会静默丢失。
测试时不要只验证方法调用次数,还要验证最终数据库状态和失败恢复记录。对 AFTER_COMMIT 来说,“真的发生过提交”是测试成立的前提。
常见问题
不写 phase 会在什么时候执行?
@TransactionalEventListener 的默认阶段是 AFTER_COMMIT。显式写出它有助于表达业务意图。
监听器抛异常会回滚原事务吗?
不会。进入 AFTER_COMMIT 时原事务已经提交。异常只能由监听器自己的重试、补偿或新事务处理。
提交后只发 HTTP 请求,还需要 REQUIRES_NEW 吗?
HTTP 调用本身不需要数据库事务;但应配置超时、幂等键和失败恢复。若还要记录发送结果,则该记录应进入新事务或可靠任务表。
可以直接把 JPA 实体放进事件吗?
不建议。提交后实体可能已脱离持久化上下文,延迟加载也可能失败。传递 ID 和必要快照更清晰。
正确的实现顺序是:先在活动事务内发布轻量事件,用 AFTER_COMMIT 绑定成功结果;提交后需要落库时使用独立 Bean 的 REQUIRES_NEW;需要降低延迟时再加受控异步;任务不可丢时升级为事务 Outbox。这样才能同时守住提交时机、数据边界和故障恢复能力。
Go 如何流式转发 multipart 文件而不落盘
- 上一篇
- Go 如何流式转发 multipart 文件而不落盘
- 下一篇
- multipart 表单读取时报 message too large 怎么处理
-
- 文章 · java教程 | 3小时前 | 配置管理 · Java · Spring Boot · 故障排查 · Java教程 · spring boot actuator @ConfigurationProperties 配置属性绑定 PropertySource
- Spring Boot 配置属性绑定失败怎样定位字段来源
- 184浏览 收藏
-
- 文章 · java教程 | 5小时前 | Java ·
- Java Vector API 怎样批量计算浮点数组
- 319浏览 收藏
-
- 文章 · java教程 | 8小时前 | Java · java Class-File API LineNumberTable
- Java Class-File API 如何重写方法的行号表属性
- 418浏览 收藏
-
- 文章 · java教程 | 10小时前 | Java · 性能优化 · Java教程 · arena 内存映射 超大文件 FileChannel.map Java MemorySegment
- Java 内存段怎样安全映射超大文件
- 435浏览 收藏
-
- 文章 · java教程 | 12小时前 | Java · record pattern Java记录模式 嵌套record Java模式匹配 Java switch模式
- Java 记录模式怎样拆解嵌套数据对象
- 308浏览 收藏
-
- 文章 · java教程 | 14小时前 |
- Java 虚拟线程批量发起网络请求时如何限制并发度
- 265浏览 收藏
-
- 文章 · java教程 | 16小时前 | Java · 并发编程 · 虚拟线程 · java 结构化并发 StructuredTaskScope 子任务取消
- Java 结构化并发怎样统一取消一组子任务
- 370浏览 收藏
-
- 文章 · java教程 | 18小时前 | 并发 · Java · java threadlocal ScopedValue 并发上下文
- Java ScopedValue 如何替代只读 ThreadLocal 上下文
- 457浏览 收藏
-
- 文章 · java教程 | 21小时前 |
- Java 序列化边界怎么收紧:白名单与替代格式
- 278浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 异常处理 · AutoCloseable Java try-with-resources suppressed exception 关闭顺序 getSuppressed
- try-with-resources 关闭顺序会如何影响主异常
- 197浏览 收藏
-
- 文章 · java教程 | 1天前 | Java教程 · ServiceLoader module-info Java模块 uses provides
- 模块化项目为什么读不到服务实现:uses 与 provides 排查
- 462浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 取消 · CompletableFuture · 重试 ·
- Java HTTP Client 实现带取消与重试的异步请求
- 212浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 393次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 471次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 478次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 421次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 246次使用
-
- 自学七个月Java能找到实习的工作吗?
- 2023-02-16 440浏览
-
- 当初转行学习Java很正确,不然不会有今天的高薪
- 2023-02-22 236浏览
-
- node.js学习笔记之koa框架和简单爬虫练习
- 2023-01-10 124浏览
-
- 应届生想要找到一份Java开发的工作,需要达到什么水平?
- 2023-02-25 374浏览
-
- HashMap的实现原理(看这篇就够了)
- 2023-01-19 443浏览

