Skip to content

事务绑定事件 (Transaction-bound Events)

从 Spring 4.2 开始,事件监听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成后处理事件。这样做可以让事件在当前事务的结果对监听器真正重要时,被更灵活地使用。

你可以使用 @EventListener 注解注册一个常规的事件监听器。如果你需要将其绑定到事务,请使用 @TransactionalEventListener。当你这样做时,监听器默认绑定到事务的提交阶段 (commit phase)

下一个示例展示了这个概念。假设一个组件发布了一个订单创建事件,我们希望定义一个监听器,该监听器仅在发布该事件的事务成功提交后才处理该事件。以下示例设置了这样一个事件监听器:

java
@Component
public class MyComponent {

	@TransactionalEventListener
	public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
		// ...
	}
}
kotlin
@Component
class MyComponent {

	@TransactionalEventListener
	fun handleOrderCreatedEvent(creationEvent: CreationEvent<Order>) {
		// ...
	}
}

@TransactionalEventListener 注解暴露了一个 phase 属性,允许你自定义监听器应绑定到的事务阶段。有效的阶段包括 BEFORE_COMMITAFTER_COMMIT(默认)、AFTER_ROLLBACK 以及 AFTER_COMPLETION(聚合了事务完成,无论是提交还是回滚)。

如果没有事务正在运行,监听器根本不会被调用,因为我们无法遵守所需的语义。但是,你可以通过将注解的 fallbackExecution 属性设置为 true 来覆盖该行为。

注意

从 6.1 开始,@TransactionalEventListener 既可以与由 PlatformTransactionManager 管理的线程绑定事务一起使用,也可以与由 ReactiveTransactionManager 管理的响应式事务一起使用。对于前者,监听器保证能看到当前线程绑定的事务。由于后者使用 Reactor 上下文而不是线程局部变量,事务上下文需要作为事件源包含在发布的事件实例中。有关详细信息,请参阅 TransactionalEventPublisher javadoc。


补充教学

1. 经典应用场景:解耦与数据一致性

@TransactionalEventListener 是解决分布式系统中最常见的“双写一致性”问题的利器。 场景:用户注册成功后,发送欢迎邮件/短信。

  • 错误做法:在 Service 的 @Transactional 方法中直接发邮件。
    • 如果邮件发了,但随后数据库提交失败(回滚),用户收到了邮件却登录不了。
  • 正确做法:Service 方法只负责存库并发布事件。监听器使用 @TransactionalEventListener(phase = AFTER_COMMIT)
    • 只有数据库事务真的提交了,才会触发发邮件的动作。

2. "事务已关闭" 陷阱

这是一个极其常见的坑:在 AFTER_COMMIT 阶段的监听器中,尝试修改数据库。

java
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onUserCreated(UserCreatedEvent event) {
    // 错误!此时原有事务已经 Commit 并关闭了。
    // 这里的 save 操作要么在无事务环境下运行,要么开启一个全新的事务(取决于传播行为配置)。
    // 它绝对不会也是原有事务的一部分!
    userRepository.save(newLog); 
}

解决方案

  • 如果必须在原来的事务中执行,请改用 BEFORE_COMMIT(但要注意异常可能会导致整个事务回滚)。
  • 如果这是一个独立的后续操作,请确保新的操作开启了新的事务(例如使用 REQUIRES_NEW)。

3. 结合 @Async 实现异步解耦

默认情况下,@TransactionalEventListener 仍然是在由于提交事务的同一个线程中同步执行的。如果监听器里的操作很耗时(如发邮件),会阻塞主线程的返回。 最佳实践:同时加上 @Async

java
@Async
@TransactionalEventListener
public void handleEvent(OrderCreatedEvent event) {
    // 在事务提交后,由于独立的线程异步执行
}

结合使用这两个注解,可以实现既确保数据一致性(提交才发),又确保高性能(异步执行)的完美方案。

Based on Spring Framework.