背景:
例如项目中我们新注册用户,注册完成后,给用户发送短信。注册是一个动作,发短信是一个动作,如果注册成功了,短信发送失败了,而不应该影响到用户注册,这个时候我们就需要对注册进行事务监听,注册成功了我们再去发短信,如果注册失败就直接不发短信。
解决方案:
为了解决上述问题,Spring为我们提供了两种方式:
(1) @TransactionalEventListener注解。
(2) 事务同步管理器TransactionSynchronizationManager。
这一篇主讲@TransactionalEventListener注解
我们可以从命名上直接看出,它就是个EventListener,
在Spring4.2+,有一种叫做@TransactionEventListener的方式,能够实现在控制事务的同时,完成对对事件的处理。
我们知道,Spring的事件监听机制(发布订阅模型)实际上并不是异步的(默认情况下),而是同步的来将代码进行解耦。而@TransactionEventListener仍是通过这种方式,但是加入了回调的方式来解决,这样就能够在事务进行Commited,Rollback…等时候才去进行Event的处理,来达到事务同步的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
1 2 3 4 5 6 7 8 9 10 |
|
这里主要是为了讲解如何使用@TransactionalEventListener,所以就不列出所有代码了。
1 2 3 4 5 6 |
|
业务实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
自定义事件:
1 2 3 4 5 6 7 8 9 |
|
事件监听器:
1 2 3 4 5 6 7 8 9 10 11 |
//
|
关于事务的实现原理,这里其实是比较简单的,Spring对事务监控的处理逻辑在TransactionSynchronization中,如下是该接口的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
工作中遇到的问题:
该操作是分三个动作,第一个动作成功了,去执行第二个动作,第二个动作成功了去执行第三个动作,如果第二个动作失败,终止第三步操作同时回退第一步操作。
我的做法是:两个@TransactionalEventListener
1.监听TransactionPhase.AFTER_COMMIT
2.监听phase = TransactionPhase.AFTER_ROLLBACK
这个时候出现一个问题,我第二动作不加@Transactional和加上@Transactional默认传播机制,有事务继承无事务新建,我第二个动作无论成功和失败,监听COMMIT和ROLLBACK的事件监听都没有执行,消息已经push出去了,但是没有监听到。
经过对源码TransactionSynchronization类的注释解读:
*事务提交后调用。在后,主事务已经成功提交,可以执行进一步操作* 。例如,可以提交在主事务成功*提交之后应该进行的进一步操作,如确认消息或电子邮件。* 注意:事务已经提交,但是*事务资源可能仍然是活动的和可访问的。因此,*此时触发的任何数据访问代码仍将“参与”*原始事务,允许执行一些清理(之后不再有提交!),除非它显式声明它需要在一个单独的*事务中运行。因此:使用f@code PROPAGATION_REQUIRES_NEW)*从这里调用的事务操作。@抛出RuntimeException错误;将传播到调用者(注意:不要在这里抛出TransactionException子类!)
default void afterCommit() { }
@TransactionalEventListener和@EventListener一样,都是同步处理,即处理事件和发布事件的线程是同一个,因此仍然可能会阻塞线程,但是可以使用@Async进行异步任务处理!
所以要解决我那个问题的两种解决方案:
1.修改事务的传播方式:propagation = Propagation.REQUIRES_NEW
2.加@Async
为什么要新建事务:因为@Transactional默认传播机制继承了当前的事务,因为@TransactionalEventListener本身是同步的,所以当前事务并没有真正结束,所以要脱离当前事务就要挂起当前事务然后新建事务或者改为异步执行
延伸思考:@TransactionalEventListener同步操作失败是否会引发事务回滚,如果@TransactionalEventListener没有开启新的事务是不会引发事务回滚,即使继承当前事务也不会回滚,因为@TransactionalEventListener是commit的方法进行回调时候执行的,所以本身事务已经完成,即使监听操作失败也不影响之前的操作。需要注意的点,即使开启的新事务,也只是回滚新事务的操作,不会回滚之前的操作