背景:
例如项目中我们新注册用户,注册完成后,给用户发送短信。注册是一个动作,发短信是一个动作,如果注册成功了,短信发送失败了,而不应该影响到用户注册,这个时候我们就需要对注册进行事务监听,注册成功了我们再去发短信,如果注册失败就直接不发短信。
解决方案:
为了解决上述问题,Spring为我们提供了两种方式:
(1) @TransactionalEventListener注解。
(2) 事务同步管理器TransactionSynchronizationManager。
这一篇主讲@TransactionalEventListener注解
三、@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 |
// @since 4.2 注解的方式提供的相对较晚,其实API的方式在第一个版本就已经提供了。 // 值得注意的是,在这个注解上面有一个注解:`@EventListener`,所以表明其实这个注解也是个事件监听器。 @Target ({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @EventListener //有类似于注解继承的效果 public @interface TransactionalEventListener { // 这个注解取值有:BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION // 各个值都代表什么意思表达什么功能,非常清晰,下面解释了对应的枚举类~ // 需要注意的是:AFTER_COMMIT + AFTER_COMPLETION是可以同时生效的 // AFTER_ROLLBACK + AFTER_COMPLETION是可以同时生效的 TransactionPhase phase() default TransactionPhase.AFTER_COMMIT; // 表明若没有事务的时候,对应的event是否需要执行,默认值为false表示,没事务就不执行了。 boolean fallbackExecution() default false ; // 这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上 // 注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了。 @AliasFor (annotation = EventListener. class , attribute = "classes" ) Class[] value() default {}; @AliasFor (annotation = EventListener. class , attribute = "classes" ) Class[] classes() default {}; String condition() default "" ; } |
1 2 3 4 5 6 7 8 9 10 |
public enum TransactionPhase { // 指定目标方法在事务commit之前执行 BEFORE_COMMIT, // 指定目标方法在事务commit之后执行 AFTER_COMMIT, // 指定目标方法在事务rollback之后执行 AFTER_ROLLBACK, // 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了 AFTER_COMPLETION } |
四、代码示例
这里主要是为了讲解如何使用@TransactionalEventListener,所以就不列出所有代码了。
1 2 3 4 5 6 |
@Data public class User { private long id; private String name; private Integer age; } |
业务实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Service @Slf4j public class UserServiceImpl extends implements UserService { @Autowired UserMapper userMapper; @Autowired ApplicationEventPublisher eventPublisher; public void userRegister(User user){ userMapper.insertUser(user); eventPublisher.publishEvent( new UserRegisterEvent( new Date())); } } |
自定义事件:
1 2 3 4 5 6 7 8 9 |
@Getter @Setter public class UserRegisterEvent extends ApplicationEvent { private Date registerDate; public UserRegisterEvent(Date registerDate) { super (registerDate); this .registerDate = registerDate; } } |
事件监听器:
1 2 3 4 5 6 7 8 9 10 11 |
@Slf4j @Component public class UserListener { @Autowired UserService userService; //TransactionalEventListener本身是一个同步操作,加上Async进行异步执行 @Async @TransactionalEventListener (phase = TransactionPhase.AFTER_COMMIT, classes = UserRegisterEvent. class ) public void onUserRegisterEvent(UserRegisterEvent event) { userService.sendActivationCode(event.getRegisterDate()); } } |
五、实现原理
关于事务的实现原理,这里其实是比较简单的,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 |
public interface TransactionSynchronization extends Flushable { // 在当前事务挂起时执行 default void suspend() { } // 在当前事务重新加载时执行 default void resume() { } // 在当前数据刷新到数据库时执行 default void flush() { } // 在当前事务commit之前执行 default void beforeCommit( boolean readOnly) { } // 在当前事务completion之前执行 default void beforeCompletion() { } // 在当前事务commit之后实质性 default void afterCommit() { } // 在当前事务completion之后执行 default void afterCompletion( int status) { } } |
工作中遇到的问题:
该操作是分三个动作,第一个动作成功了,去执行第二个动作,第二个动作成功了去执行第三个动作,如果第二个动作失败,终止第三步操作同时回退第一步操作。
我的做法是:两个@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的方法进行回调时候执行的,所以本身事务已经完成,即使监听操作失败也不影响之前的操作。需要注意的点,即使开启的新事务,也只是回滚新事务的操作,不会回滚之前的操作