在使用注解事务的时候,我们需要在事务提交之后,执行某个操作。
例如,我们需要保存某个数据之后,通过MQ去异步处理消息,如果直接在事务内写的话,如果MQ无积压,处理速度比较快的话,可能会在事务提交之前,就已经请求处理了。这个时候,由于mysql事务未提交,读取到数据,是之前的数据,也就是说,如果是新增了一条数据,这个时候来处理,是处理不到的。那么我们有没有办法确保事务提交之后,再去发送这个消息呢?
- 把发送消息的代码写在事务外面
- 编程式事务
- 利用TransactionSynchronizationManager实现对事务的控制
如果把代码写在事务外面,那么就意味着,事务的方法和发送消息的方法不能写在同一个class下了,spring的注解事务是基于aop的这个情况大家都知道。如果用编程式事务,还得自己去提交什么的。出于代码的可读性和偷懒的原则,我们选择用TransactionSynchronizationManager。
我们写一段代码测试下
@Transactional
public void submit(QuestionDraft questionDraft) {
......
questionDraftManager.save(questionDraft);
log.info("----------transaction uncommitted--------");
//region 事务提交之后发生mq消息
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
log.info("-----------transaction committed-------");
if(Objects.equals(0,status)){
log.info("提交到MQ进入审核队列");
}
}
});
//endregion
}
日志级别设置为debug看看日志,可以看到,这个日志是在事务提交之后才执行的。
那么,这个TransactionSynchronizationManager 是怎么做到呢?我们看下源码。
可以看到,这里是将这些TransactionSynchronization注册到当前的ThreadLocal上,那么注册上去之后又是怎么使用的呢?我们根据类的引用找到了TransactionSynchronizationUtils,可以看到里面会去遍历触发相关的函数。
顺着TransactionSynchronizationUtils 网上找,在AbstractPlatformTransactionManager 这个抽象类中找到了这样的一段
这就清晰了,顺着调用可以看到这些方法最终会被事务管理器调用。
很多资料上在事务执行之后,是使用的afterCommit ,其实要看自己的需求,afterCommit 只是事务提交了,但是提交的结果还没有得到,比较保险的是用afterCompletion,可以根据事务提交的结果来执行自己的逻辑。