Spring 事务同步机制 ------ AfterTransactionActionCollector 的异步解耦原理
一、核心概念
什么是"事务同步"(TransactionSynchronization)
Spring 提供了 TransactionSynchronizationManager,允许你在事务生命周期的不同阶段注册回调:
事务开始 → 业务逻辑执行 → 事务提交/回滚 → 事务后回调执行
关键点:事务后回调中的代码,不在原事务的保护范围内。 即使回调中的代码抛出异常,原事务也不会回滚------因为它已经提交了。
为什么能实现"解耦"
这里说的"异步"不是线程级别的异步(不是新开线程),而是事务级别的解耦:
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 执行时机 | 在事务内同步执行 | 事务提交后执行 |
| 异常传播 | 发货异常 → 事务回滚 → 审核失败 | 发货异常 → 仅记日志 → 审核已成功 |
| 数据一致性 | 强一致(要么全成功要么全失败) | 最终一致(审核一定成功,发货可能失败) |
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、Spring 事务生命周期
┌─────────────────────────────────────────────────────────┐
│ 数据库事务 │
│ │
│ ① beginTransaction │
│ ② 执行业务SQL(INSERT/UPDATE) │
│ ③ commit / rollback │
│ │
└─────────────────────────────────────────────────────────┘
│
▼ commit 成功后
┌─────────────────────────────────────────────────────────┐
│ 事务后回调(afterCommit) │
│ │
│ ④ 执行注册的回调逻辑 │
│ - 此时事务已提交,数据已持久化 │
│ - 回调中的异常不会导致事务回滚 │
│ │
└─────────────────────────────────────────────────────────┘
三、类比理解
类比:快递签收
想象你在网上买东西:
改造前(强耦合):
快递员要求你当场拆开验货,如果商品有问题,你不能签收。 → 验货失败 = 签收失败
改造后(解耦):
快递员把包裹给你,你签收。回家后拆开验货,如果有问题就联系客服退换。 → 签收一定成功,验货问题后续单独处理
四、Spring 原生 API 示例
方式一:直接使用 TransactionSynchronizationManager
java
@Service
public class OrderService {
@Transactional
public void createOrder(OrderDto orderDto) {
// ① 事务内:保存订单(这个操作受事务保护)
orderRepository.save(orderDto);
// ② 注册事务后回调(事务提交后才执行)
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// ③ 这里的代码在事务提交后执行
// 即使这里抛异常,订单也已经保存成功了
try {
messageSender.sendOrderCreatedNotification(orderDto.getId());
} catch (Exception e) {
log.warn("发送通知失败,但订单已创建成功", e);
}
}
}
);
}
}
方式二:使用 TransactionalEventListener(Spring 4.2+)
java
// 定义事件
public class OrderCreatedEvent {
private final Integer orderId;
public OrderCreatedEvent(Integer orderId) {
this.orderId = orderId;
}
}
// 发布事件(在事务内)
@Service
public class OrderService {
@Resource
private ApplicationEventPublisher eventPublisher;
@Transactional
public void createOrder(OrderDto orderDto) {
orderRepository.save(orderDto);
// 事件在事务提交后才会被消费
eventPublisher.publishEvent(new OrderCreatedEvent(orderDto.getId()));
}
}
// 监听事件(事务提交后执行)
@Component
public class OrderEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreated(OrderCreatedEvent event) {
// 事务已提交,这里的异常不会回滚订单
notificationService.sendNotification(event.getOrderId());
}
}
方式三:封装工具类(AfterTransactionActionCollector)
java
/**
* 事务后动作收集器(简化版,演示原理).
*/
public class AfterTransactionActionCollector implements TransactionSynchronization {
private final List<Runnable> commitActions = new ArrayList<>();
/**
* 添加事务提交后要执行的动作.
*/
public void addCommitSyncAction(Runnable action) {
commitActions.add(action);
}
@Override
public void afterCommit() {
// 事务提交后,依次执行所有注册的动作
for (Runnable action : commitActions) {
action.run();
}
}
}
使用方式:
java
@Transactional
public void businessMethod() {
// 事务内的数据库操作
repository.save(entity);
// 创建收集器,注册事务后动作
AfterTransactionActionCollector collectors = new AfterTransactionActionCollector();
collectors.addCommitSyncAction(() -> {
// 这段代码在事务提交后执行
doSomethingAfterCommit();
});
TransactionSynchronizationManager.registerSynchronization(collectors);
// 方法正常结束 → 事务提交 → afterCommit 被调用 → lambda 执行
}
五、执行时序对比
改造前:同步耦合
线程A:
│
├── @Transactional 开始
├── 更新审核状态(SQL)
├── 调用发货方法
│ ├── 校验闸口 → 抛异常!
│ └── ✗ 异常向上传播
├── ✗ 事务回滚(审核状态未更新)
└── ✗ 接口返回失败
改造后:事务后解耦
线程A:
│
├── @Transactional 开始
├── 更新审核状态(SQL)
├── 注册事务后回调(仅注册,不执行)
├── ✓ 事务提交成功(审核状态已更新)
│
├── [afterCommit 触发]
│ ├── 调用发货方法
│ │ ├── 校验闸口 → 抛异常!
│ │ └── ✗ catch 住异常
│ ├── 发送站内信通知客户
│ └── 继续执行(不影响已提交的事务)
│
└── ✓ 接口返回成功
六、关键问题 Q&A
Q1:afterCommit 是不是新线程执行的?
不是。 afterCommit 默认在同一个请求线程中执行。所谓"异步"是指它在事务边界之外执行,不是线程异步。
如果 afterCommit 中的逻辑很耗时,接口响应时间仍然会受影响。如果需要真正的线程异步,可以在 afterCommit 中提交到线程池或发 MQ。
Q2:如果事务回滚了,afterCommit 会执行吗?
不会。 afterCommit 只在事务成功提交后触发。如果事务回滚,会触发 afterCompletion(STATUS_ROLLED_BACK),但不会触发 afterCommit。
Q3:afterCommit 中能读到事务里写的数据吗?
能。 因为事务已经提交,数据已经持久化到数据库。其他事务也能读到。
Q4:afterCommit 中开启新事务能回滚吗?
能。 afterCommit 中如果用 @Transactional(propagation = REQUIRES_NEW) 标注的方法,会开启一个独立的新事务。这个新事务可以正常回滚,但不会影响已经提交的主事务。
七、适用场景总结
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 核心操作成功后发通知/推送 | 事务后回调 | 通知失败不应阻塞主操作 |
| 主操作成功后触发非关键副作用 | 事务后回调 | 副作用失败可接受 |
| 需要确保数据已落库才触发的下游操作 | 事务后回调 | 避免下游读到未提交的脏数据 |
| 需要完全异步 + 高并发 + 重试 | MQ | 真正的系统级解耦 |
| 跨服务调用 + 最终一致性 | MQ | 网络隔离,失败重试 |