bigywater.tech
Spring · 38

事务与 @Async、@TransactionalEventListener:提交之后再做事

想在事务提交之后再发消息、写审计、清缓存,该用什么?@EventListener 加 @Async 为什么会读不到刚下的订单?AFTER_COMMIT 里还能写数据库吗?监听器失败了调用者知道吗?这篇在 Spring Framework 7.0.9 和 MySQL 26.7.0 上逐项验证。

90 对 100
事务里 publishEvent、@Async @EventListener 监听器读得到刚下订单的次数,与 AFTER_COMMIT 的 100 次对比(各 100 次下单)
启动失败
@Transactional(REQUIRED) 的 @TransactionalEventListener 方法在 Spring 7.0.9 里的表现
不传给调用者
AFTER_COMMIT 监听器抛的异常只记 ERROR 日志

各阶段

实测 · 本机执行输出
  --- 事务正常提交
  1. place():insert 完,发布 Placed 事件
  2.   [普通 @EventListener] 收到 1
  3. place():publishEvent 返回
  4.   [BEFORE_COMMIT] 收到 1
  5.   [AFTER_COMMIT + fallbackExecution = true] 收到 1
  6.   [AFTER_COMPLETION] 收到 1
  7.   [AFTER_COMMIT] 收到 1
  --- 事务回滚
  1. place():insert 完,发布 Placed 事件
  2.   [普通 @EventListener] 收到 2
  3. place():publishEvent 返回
  4.   [AFTER_ROLLBACK] 收到 2
  5.   [AFTER_COMPLETION] 收到 2
  6. 调用者收到 IllegalStateException
  --- 没有事务时直接 publishEvent
  1.   [AFTER_COMMIT + fallbackExecution = true] 收到 3
  2.   [普通 @EventListener] 收到 3

普通监听器立即执行,事务监听器在 BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION 阶段执行;没有事务默认丢弃。

监听器里写库

实测 · 本机执行输出
  AFTER_COMMIT 监听器里的写入,事务提交之后 audit 表里有:[REQUIRES_NEW, NOT_SUPPORTED, PLAIN]
  启动失败:IllegalStateException: @TransactionalEventListener method must not be annotated with @Transactional unless when declared as REQUIRES_NEW or NOT_SUPPORTED: public void BadListener.required(Placed)

REQUIRES_NEW 与 NOT_SUPPORTED 可以,REQUIRED 启动就被拒绝。

异步监听

实测 · 本机执行输出
  @Async @EventListener(事件在事务里发布):100 次下单,监听器被调用 100 次,其中读得到刚下的那个订单的有 90 次
  @Async @TransactionalEventListener(AFTER_COMMIT):100 次下单,监听器被调用 100 次,其中读得到刚下的那个订单的有 100 次

提交之后再异步处理要用 AFTER_COMMIT,否则可能读不到刚提交的数据。

监听器失败

实测 · 本机执行输出
  LOG ERROR TransactionSynchronizationUtils: TransactionSynchronization.afterCompletion threw exception
  --- AFTER_COMMIT 监听器抛异常
  1. place():insert 完,发布 Placed 事件
  2. place():publishEvent 返回
  3. place() 正常返回
  4. 表 orders 里有 1 行

AFTER_COMMIT 监听器的异常只记日志,调用者不知道。

分步演示

提交之后再做事
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9 + @EnableAsync + DataSourceTransactionManager + HikariCP(连接池 10)+ 自己起的 MySQL 26.7.0;第三节的 90 次是这次运行的结果,每次运行不同(开发中另一次是 84 次);同一个提交里各监听器的先后顺序我没有找到规律,只记录了这次的顺序
    • 没有验证:@TransactionalEventListener 配合 @Order 的顺序;事务内发布多个事件的顺序;响应式事务的事件;Outbox 表方案(见后面的 Outbox 篇);Spring 6.1 之前同一个场景下 REQUIRED 监听器的静默丢失行为(这篇只验证了 7.0.9 在启动时就拒绝);AFTER_COMMIT 监听器执行时 afterCommit 与 afterCompletion 的具体回调点
    京ICP备2026067502号-1京公网安备11010502063487号