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 网络隔离,失败重试
相关推荐
Lightpwd1 小时前
PostgreSQL jsonb 写入踩坑:一条 `\u0000` 如何让整批同步失败,还把异常伪装成“数据完整性冲突”
java
Csvn1 小时前
📊 SQL 入门 Day 19:事务与隔离级别
后端·sql
周周哈哈哈1 小时前
线程创建、执行、退出、回收
java·开发语言
云边有个稻草人1 小时前
飞算JavaAI能处理SaaS套餐变更业务吗?
java·springboot·ai编程·java开发·飞算javaai·java代码生成·ai coding模型
面试鸭1 小时前
Anthropic 让 Claude 接了 CI 值班的第一班,人剩下的是写规则和合 PR
后端·面试
小强19881 小时前
useRef 到底用来干什么?不止获取 DOM 元素
后端
大勇前进1 小时前
React Hooks 常见 5 大坑:写完代码疯狂 Bug,避坑总结
后端
颜进强1 小时前
04 - 一张图看懂 OpenSpec 变更的一生:从创建到归档
前端·后端·ai编程
Cobyte1 小时前
Claude Code 的 Task System 的实现原理
后端·aigc·ai编程