概述
业务上经常会有一些需求是需要在某个数据库操作事务提交之后再去操作。
我常用的就方式有@TransactionalEventListener
和TransactionSynchronizationManager
.
其实@TransactionalEventListener
背后使用的也是TransactionSynchronizationManager
。
注意点:在afterCompletion
方法中已经脱离当前事务了,如果想确保其中的方法也在一个事务中,那么就需要开启一个新事务.
在使用 >= spring 5.2
是可以用org.springframework.transaction.support.TransactionTemplate#execute
方法,里面会新开启一个事务。在小于上述版本时,可以手动开启事务来控制.
使用demo
//fallbackExecution = true代表没有事务的时候也监听
@TransactionalEventListener(value = {xxx.class}, fallbackExecution = true)
@Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(xxx event) {
log.info("监听成功,xxxxxx");
//xxxxx
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
switch (status) {
case 0:
// 外部方法事务提交后执行的业务逻辑
break;
case 1:
// 外部方法事务回滚后执行的业务逻辑
break;
case 2:
// 外部方法事务异常时执行的业务逻辑
break;
}
}
});
TransactionSynchronizationManager(事务同步器)分析
平时使用的时候都是直接用注解@Transactional
,一般也知道是spring aop
帮我们开启了事务,其中aop
拦截使用事务注解的方法后使用的就是TransactionSynchronizationManager
.
我用的是mybatis-spring-2.0.7
,就从mybtais
获取connection
来入手了
org.mybatis.spring.transaction.SpringManagedTransaction#openConnection
在从连接池获取connection
的时候会判断是否开启了事务,如果已开启,那么会将连接暂时绑定到事务同步管理器中.
所谓的绑定其实就是塞到了一个threadLocal
中和当前线程进行了一个关联。
业务中对事务的扩展方法通常都是在TransactionSynchronization
的方法中执行的.
在org.springframework.transaction.support.AbstractPlatformTransactionManager#triggerAfterCompletion
中可以看到会将所有之前注册的同步事务操作按照List
中的顺序(注册顺序)依次调用.
最后在org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
中的finally
中可以看到对各项资源进行了释放,意味着我们仍旧可以在这之前拿到connection
连接进行操作db
。
注意点:
如果在A
事务中注册了一个同步事务,在同步事务回调中调用了一个B
事务方法(B
事务方法中又注册了另一个同步事务),要注意B
事务的传播等级需要为Propagation.REQUIRES_NEW
,不然后一个同步事务回调中的事务状态会变成TransactionSynchronization.STATUS_UNKNOWN
.
debug了一下源码,发现在A
事务提交完成后,在A
事务的同步回调中的B
事务还会加入A
已经完成的事务中;
想到spring
的事务是基于connection
连接,而连接又是通过ThreadLocal
和当前线程绑定(org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
);
当A事务提交后,当前线程仍持有之前的连接,该连接在A事务完成之后并没有被重置,此时B事务时仍复用了当前连接;
导致B
事务加入到了A
事务中,但是此时A
事务已经完成,B
加入的是一个无效事务;
此时B
事务中有一个同步事务回调在,按照默认的事务同步配置(AbstractPlatformTransactionManager.SYNCHRONIZATION_ALWAYS
),会重新new一个事务状态,这个新new
的事务状态导致在回调中会触发TransactionSynchronization.STATUS_COMMITTED
;
最终导致B
的事务同步回调中的状态不对;
综上所述,如果在注册事务同步方法之间有嵌套,最好使用PropagationREQUIRES_NEW
传播方式
获取连接绑定connection给TransactionSynchronizationManager
TransactionSynchronization 扩展点
public interface TransactionSynchronization extends Ordered, Flushable {
/** Completion status in case of proper commit. */
// 事务提交状态
int STATUS_COMMITTED = 0;
/** Completion status in case of proper rollback. */
// 事务回滚状态
int STATUS_ROLLED_BACK = 1;
/** Completion status in case of heuristic mixed completion or system errors. */
// 事务异常状态
int STATUS_UNKNOWN = 2;
/**
* 当前事务挂起时触发
* Supposed to unbind resources from TransactionSynchronizationManager if managing any.
* @see org.springframework.transaction.reactive.TransactionSynchronizationManager#unbindResource
*/
default void suspend() {
}
/**
* 当前事务恢复时触发(结束挂起时)
* Supposed to rebind resources to TransactionSynchronizationManager if managing any.
* @see org.springframework.transaction.reactive.TransactionSynchronizationManager#bindResource
*/
default void resume() {
}
/**
* 当前事务刷新时触发
* 将基础会话刷新到数据存储区(如果适用),比如Hibernate/JPA的Session
* @see org.springframework.transaction.TransactionStatus#flush
*/
@Override
default void flush() {
}
/**
* 当前事务提交前触发,此处若发生异常,会导致回滚。
* @see #beforeCompletion
*/
default void beforeCommit(boolean readOnly) {
}
/**
* 当前事务完成前触发。
* 在beforeCommit之后,commit/rollback之前执行。即使异常,也不会回滚。
* @see #beforeCommit
* @see #afterCompletion
*/
default void beforeCompletion() {
}
/**
* 当前事务提交后触发
*/
default void afterCommit() {
}
/**
* 当前事务完成后触发
* 通过status的value指定具体的触发时机(即上面的三个属性)
*/
default void afterCompletion(int status) {
}
}