Spring 事务同步机制 —— AfterTransactionActionCollector 的异步解耦原理

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 网络隔离,失败重试
相关推荐
神仙别闹1 小时前
基于 C++ 实现两个有序链表序列的交集
java·c++·链表
swordbob2 小时前
ReentrantLock 与 AQS 完整学习手册
java·开发语言
m0_587383003 小时前
全民健身解决方案软件开发实战:从架构设计到落地指南
java·spring boot·spring·架构·需求分析
白山编程大哥3 小时前
Java OutputStreamWriter 详解:从字符到字节的桥梁
java·开发语言·python
wno7043 小时前
Spring Cloud Hystrix服务容错
spring·spring cloud·hystrix
一技安身4 小时前
【信创】Docker‑Compose V2 两种离线部署(独立模式、插件模式)简易教程
java·docker·eureka
七夜zippoe4 小时前
为什么 2026 年每个 Java 团队都该懂 AI Agent
java·开发语言·人工智能
时凌云.5 小时前
【2026最新】JDK 下载安装与环境配置全教程(Windows/Mac/Linux 三平台,零基础友好)
java·linux·macos
Despacito10065 小时前
Java后端性能探查工具速查表
java·开发语言
IT_陈寒5 小时前
Python的多线程就是个假把式,我算是体验到了
前端·人工智能·后端