TransactionSynchronization#afterCommit的一次应用

938 阅读3分钟

前言

    在一次生产问题排查中,发现对应的业务同步的异步任务执行失败了,报错原因是同步的主表数据不存在,重试异步任务后,任务却执行成功了,好奇怪的现象,说明当异步任务第一次执行时,数据库还没有存在对应的数据?

原因分析

业务代码简单如下

@Transactional
public void addOrder(OrderSaveDTO dto) {
    ...
    this.saveOrder(order);
    taskService.releaseTask(taskDTO);
}

代码逻辑就是先创建订单对应的业务,然后发布异步任务去同步订单所关联的业务,例如库存、物流等业务,异步任务实现是通过发MQ消息去异步执行业务,在异步任务执行中,还需要查询订单信息去同步数据,根据报错原因说明任务执行时,订单数据还没有入库,问题点就是这里了,#saveOrder方法执行了,但是对应的数据库事务还没有提交成功,就发布异步任务去同步数据,导致了异步任务执行数据库数据不一致的问题产生。

在业务数据的事务还没有执行完消息就已经发出去了, 导致后续的一些数据或逻辑上的问题产生。

解决方案

    那逻辑要怎么完善呢?业务数据的事务还没执行完,我们就发异步任务去同步关联数据,那我们就等业务数据的事务提交后,再发异步任务去同步关联数据不就行了。

利用TransactionSynchronizationManager的registerSynchronization()方法注册TransactionSynchronization实现类

改造点很简单,我们只需要在原有的业务方法中添加如下代码, 就可以完成在事务提交后去处理后续的业务逻辑,有一个点很重要,就是一定要使用Spring的事务管理,要在业务方法上加上@Transactional,不然代码是没有效果的,你都没开启事务管理,哪有什么事务提交的后续逻辑处理

@Transactional
public void addOrder(OrderSaveDTO dto) {
    ...
    this.saveOrder(order);
  TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
       @Override
       public void afterCommit() {
          taskService.releaseTask(taskDTO);
       }
    });
}

原理

Spring 事务的扩展 – TransactionSynchronization

事务操作的时候它的当前线程还保存了 TransactionSynchronization 对象。而这个对象伴随着 Spring 对 事务处理的各个生命周期都会有相应的扩展

public interface TransactionSynchronization extends Flushable {
 
    /** 事务提交状态 */
    int STATUS_COMMITTED = 0;
 
    /** 事务回滚状态 */
    int STATUS_ROLLED_BACK = 1;
 
    /**系统异常状态 */
    int STATUS_UNKNOWN = 2;
 
    void suspend();
 
    void resume();
 
    void flush();
 
    // 事务提交之前
    void beforeCommit(boolean readOnly);
 
    // 事务成功或者事务回滚之前
    void beforeCompletion();
 
    // 事务成功提交之后
    void afterCommit();
 
    // 操作完成之后(包含事务成功或者事务回滚)
    void afterCompletion(int status);
 
}

我们所使用的TransactionSynchronization#afterCommit方法,在Spring中对于处理事务完成后执行特定逻辑的一个回调方法。具体来说,当事务成功提交后,Spring 会调用已注册的TransactionSynchronizationafterCommit 方法,以便执行一些事务后的清理工作或者触发其它相关的操作。