在Spring事务提交后做点事儿

2019-03-08  本文已影响0人  六月星空2011

一、背景

最近团队整理出的一份《Rabbit MQ消息定义规范》后,有同学提出了这样的一个场景, 在事务还没有执行完消息就已经发出去了, 导致后续的一些数据或逻辑上的问题产生, 那么既然出现了问题, 我们就需要解决这个问题, 正好这段时间在看Spring事务相关的知识, 所以本文就是带着这样的问题, 给出一些解决此问题的方案, 供大家参考.

二、方案核心

本文整理了三种解决方案, 但是在给出解决方案之前, 我们需要了解一下这三种方案的技术核心点是什么, 因为这是重点中的重点, 因为三种方案背后的本质逻辑都来源于此.
原理核心: Spring在事务正常提交完成后, 我们会看到这样的一段代码(更多源码分析移步参考文章二):

// AbstractPlatformTransactionManager#commit
// 到此事务已经正常提交结束了
try {
   // 这里就是三种方案的核心入口
   triggerAfterCommit(status);
} finally {
   // 事务回滚只会有这个
   triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}

这个代码有什么用呢? 请看下面

// TransactionSynchronizationUtils#invokeAfterCommit
public static void invokeAfterCommit(List<TransactionSynchronization> synchronizations) {
   if (synchronizations != null) {
      for (TransactionSynchronization synchronization : synchronizations) {
         // 执行注册的TransactionSynchronization实现类的afterCommit方法
         synchronization.afterCommit();
      }
   }
}

到这里应该就明白了, 我们要在事务提交结束后做点事情, 只需要想办法搞个TransactionSynchronization的实现类, 并把这个实现类注册到synchronizations中就可以实现我们想要的功能逻辑处理了, 那么接下来看看下面的解决方案是怎样利用这一点, 解决问题的.
注意: 这里执行afterCommit()即使抛异常了, 也不会导致事务回滚.

三、解决方案

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

我们只需要在执行的事务方法中, 添加如下代码, 就可以完成在事务提交后的逻辑处理了

// TransactionSynchronizationAdapter是TransactionSynchronization的默认实现
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
    @Override
    public void afterCommit() {
        // 事务提交后需要执行的业务逻辑: 发消息, 日志...
    }
});

当然在每个事务方法里面写这么一堆, 并不有优雅, 所以可以写了一个简单的工具类供使用(可以根据实际使用优化):

public class TransactionUtil {
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(5,
            20, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20), new ThreadPoolExecutor.AbortPolicy());
 
    public static void afterCommit(Runnable runnable) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                try {
                    THREAD_POOL_EXECUTOR.execute(runnable);
                } catch (Exception e) {
                    // 记录日志
                }
            }
        });
    }
}
// 调用就变成了
TransactionUtil.afterCommit(() -> System.out.println("测试事务提交后的逻辑处理"));
方案2.利用Spring 4.2版本的新特性@TransactionalEventListener注解的事件机制来完成事务提交后的逻辑处理
@Autowired
private ApplicationEventPublisher publisher;
 
@Override
@Transactional(rollbackFor = Exception.class)
public void add(AdvanceChargeApplyAddInput input) {
    this.save(advanceChargeApply);
    // 发送事件
    publisher.publishEvent(advanceChargeApply);
}
// 响应事件, 事务提交后执行
@TransactionalEventListener
public void handle(PayloadApplicationEvent<AdvanceChargeApply> event) {
    System.out.println("TransactionalEventListener 事务提交后执行");
}

本方案的原理是ApplicationListenerMethodTransactionalAdapter内部封装了@TransactionalEventListener注解, 添加了一个适配器ApplicationListenerMethodTransactionalAdapter(继承了TransactionSynchronizationAdapter)内部通过TransactionSynchronizationManager.registerSynchronization() 注册一个TransactionSynchronization, 然后执行afterCommit()时, 会调用ApplicationListenerMethodAdapter#processEvent(), 然后通过反射调用handle()方法.

方案3. 方案的核心依然是继承TransactionSynchronizationAdapter, 然后通过TransactionSynchronizationManager.registerSynchronization() 注册一个TransactionSynchronization, 实现事务提交后的处理逻辑.

四、总结

三种方案都利用了Spring在事务提交后的逻辑处理机制, 实现了不同的解决方案. 每种方案也各有优缺点, 在此已简单列举, 私以为方案一最简单, 对spring版本依赖也更小, 可以优先考虑.
 本文主要是讨论事务正常提交后的逻辑处理方式, 如果要在事务前或回滚后做逻辑处理, 同样可以通过实现TransactionSynchronization不同的方法实现, 原理如本文.
 最后, 欢迎大家对本文的方案进行讨论补充和完善.

参考文章:
 http://ifeve.com/spring4-2/
 https://github.com/xmw9160/spring-framework-3.2.x
 http://azagorneanu.blogspot.com/2013/06/transaction-synchronization-callbacks.html

上一篇下一篇

猜你喜欢

热点阅读