在一些业务场景中可能我们需要去对某一个spring事务的生命周期进行监控,比如在这个事务提交,回滚,被挂起的时候,我们想要去执行一些自定义的操作,这怎么去做呢?其实spring作为一个高扩展性的框架,早就提供好了这一个扩展点,这个扩展点就是事务同步器TransactionSynchronization
使用方式
public interface TransactionSynchronization extends Flushable {
int STATUS_COMMITTED = 0;
int STATUS_ROLLED_BACK = 1;
int STATUS_UNKNOWN = 2;
default void suspend() {
}
default void resume() {
}
@Override
default void flush() {
}
default void beforeCommit(boolean readOnly) {
}
default void beforeCompletion() {
}
default void afterCommit() {
}
default void afterCompletion(int status) {
}
}
可以看到,TransactionSynchronization是一个接口,它里面定义了一系列与事务各生命周期阶段相关的方法。比如,我们可以这样使用:
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
System.out.println("saveUser事务已提交...");
}
});
userDao.saveUser(user);
}
在spring事务刚开始的时候,我们通过TransactionSynchronizationManager事务同步管理器去注册一个事务同步器,当spring事务提交完之后,就会去回调当前这个spring事务所注册的所有事务同步器(一个spring事务可以注册多个事务同步器)的afterCommit方法。需要注意的是注册事务同步器必须得在一个spring事务中才能注册,否则会抛出Transaction synchronization is not active这个错误
为何事务同步器必须要在spring事务中才能注册?
具体原因我们可以通过源码中找到:
public static void registerSynchronization(TransactionSynchronization synchronization)
throws IllegalStateException {
Assert.notNull(synchronization, "TransactionSynchronization must not be null");
Set<TransactionSynchronization> synchs = synchronizations.get();
if (synchs == null) {
throw new IllegalStateException("Transaction synchronization is not active");
}
synchs.add(synchronization);
}
可以看到如果synchronizations.get()返回的是null,那么就会抛出这个错误,而什么时候这里才不会返回null呢?我们可以去看spring事务创建的时候,代码如下:
每一次创建一个新的spring事务的时候都会去调用startTransaction方法,而在startTransaction方法中会调用prepareSynchronization方法,这个方法中做的事情主要就是去把新创建的spring事务的一些信息放到线程上下文中(在这个spring事务执行期间我们都可以通过事务同步管理器去拿到这些信息)。最后最关键的就是调用了initSynchronization方法,在这个方法中我们就看到了此时会初始化一个空集合放到synchronizations中,所以当执行spring事务中的业务代码的时候,此时由于synchronizations已经不为空了,所以我们就可以成功地把事务同步器注册到事务同步器管理器中了
事务同步器在多个事务之间如何切换?
事务同步器只对注册它的那个spring事务生效,如果这个spring事务中存在嵌套的spring事务,那么事务同步器就不会对嵌套的那个spring事务生效了。这可能有点难理解,我们可以直接上代码去方便理解:
上面的代码就是saveUser方法会去调用saveUser2方法,其中saveUser2方法的事务传播级别是REQUIRES_NEW,也就是saveUser和saveUser2这两个方法会处于两个不同的事务中,重点是saveUser方法还往事务同步管理器中注册了一个事务同步器去监听事务提交阶段。那么当调用saveUser方法的时候,由于会创建两个事务,此时会不会回调两次事务同步器?
可以发现事务同步器的afterCommit方法只回调了一次。那么要想回调两次怎么办?答案:在saveUser2方法中也去注册一个事务同步器:
执行结果如下:
可以看到只要我们在saveUser2方法中也去注册一个事务同步器,那么当saveUser2的事务提交的时候,就能执行到afterCommit方法了。那么为什么会这样呢?下面我们深入源码去探究:
org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// .............
// 条件成立:该事务方法所声明的事务传播级别等于PROPAGATION_REQUIRES_NEW,该级别表示需要开启一个新的事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
// 首先需要把原来的事务挂起,这里的挂起其实就是把ThreadLocal中与当前线程上下文绑定的资源对象进行解绑(remove),
// 而返回的SuspendedResourcesHolder对象中缓存了原来事务的资源对象
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
// 创建一个新的事务
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// ...............
}
当当前线程上下文中存在事务的时候,就会执行handleExistingTransaction方法,在handleExistingTransaction方法中会去判断处理不同的事务传播级别,我们这里以PROPAGATION_REQUIRES_NEW为例子,此时会调用suspend方法,并返回一个挂起的资源对象,我们进去suspend方法看看:
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// 条件成立:说明当前线程上下文中已经存在事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 回调线程上下文中所有的事务同步器,并执行其suspend方法,然后再把所有的事务同步器从线程上下文中移除
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
// 被挂起的资源对象
Object suspendedResources = null;
if (transaction != null) {
suspendedResources = doSuspend(transaction);
}
// 因为需要把当前的事务进行挂起,所以下面要做的就是把线程上下文中的当前事务信息需要被缓存起来,等到事务恢复的时候再从缓存中获取并恢复,并且后面会把新事务的信息放到线程上下文中
// 获取被挂起的事务名称
String name = TransactionSynchronizationManager.getCurrentTransactionName();
// 把线程上下文中的事务名称置为null
TransactionSynchronizationManager.setCurrentTransactionName(null);
// 获取被挂起的事务的读写模式
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
// 把线程上下文中的事务读写模式改为非只读
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
// 获取被挂起的事务的事务隔离级别
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
// 把线程上下文中的事务中的事务隔离级别改为null
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
// 获取被挂起事务的激活状态
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
// 把线程上下文中的事务激活状态置为false
TransactionSynchronizationManager.setActualTransactionActive(false);
// 返回被挂起的资源
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
} catch (RuntimeException | Error ex) {
// doSuspend failed - original transaction is still active...
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
}
// 条件成立:说明此时线程上下文中还不存在事务
else if (transaction != null) {
// 对事务资源进行挂起,具体操作由子类实现,并且返回的是原来事务的连接对象
Object suspendedResources = doSuspend(transaction);
// 把原来的资源缓存在SuspendedResourcesHolder这个挂起资源对象中
return new SuspendedResourcesHolder(suspendedResources);
} else {
// Neither transaction nor synchronization active.
return null;
}
}
private List<TransactionSynchronization> doSuspendSynchronization() {
// 获取当前线程上下文中的所有事务同步器
List<TransactionSynchronization> suspendedSynchronizations =
TransactionSynchronizationManager.getSynchronizations();
// 遍历所有的事务同步器,调用suspend方法
for (TransactionSynchronization synchronization : suspendedSynchronizations) {
synchronization.suspend();
}
// 然后把所有的事务同步器都从线程上下文中移除
TransactionSynchronizationManager.clearSynchronization();
return suspendedSynchronizations;
}
可以看到此时会把线程上下文中的事务信息以及事务同步器都取出来,然后创建一个SuppendedResourcesHolder对象,把取出来的事务信息以及事务同步器放到这个对象中,最后把这个对象返回出去
/**
* 开启事务
*
* @param definition 事务属性定义对象
* @param transaction 事务对象
* @param debugEnabled debugEnabled
* @param suspendedResources 被挂起的事务的资源对象,如果当前线程中不存在事务,则该参数对象为null
*/
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
// 只要事务同步模式不等于SYNCHRONIZATION_NEVER,那么事务同步在事务开启后都会生效
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 创建一个事务状态对象
// 这里newTransaction属性很重要,当此时是创建一个新的事务的时候,newTransaction就等于true
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 具体由子类实现
doBegin(transaction, definition);
// 事务同步器绑定到当前线程上下文
prepareSynchronization(status, definition);
return status;
}
然后由于事务传播级别是PROPAGATION_REQUIRES_NEW,所以我们需要新开一个spring事务,也就是调用startTransaction方法创建一个新的TransactionStatus,同样的需要执行prepareSynchronization方法,上面也说过了,在prepareSynchronization方法中会去把新创建的事务的信息放到线程上下文中
/**
* 为给定参数创建TransactionStatus实例
*/
protected DefaultTransactionStatus newTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
boolean actualNewSynchronization = newSynchronization &&
!TransactionSynchronizationManager.isSynchronizationActive();
return new DefaultTransactionStatus(
transaction, newTransaction, actualNewSynchronization,
definition.isReadOnly(), debug, suspendedResources);
}
在创建TransactionStatus的构造方法中,此时就会把SuppendedResourcesHolder对象作为参数被存放在DefaultTransactionStatus对象中。这样一来此时就效果就是当前线程上下文中保存的事务就是新创建的事务了,而原来的事务都会缓存到新事务的TransactionStatus对象中了。然后我们关注到事务提交的环节
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
// ...........事务提交代码省略
} finally {
// 这行代码里面处理了恢复被挂起事务的操作,也就是说恢复被挂起的事务是在新事务提交完之后去执行的
cleanupAfterCompletion(status);
}
}
在提交完事务之后,会执行cleanupAfterCompletion方法
/**
* 当事务完成后会调用,进行一些清理以及恢复操作
*
* @param status 事务状态对象
* @see #doCleanupAfterCompletion
*/
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
// 标记事务状态为已完成
status.setCompleted();
if (status.isNewSynchronization()) {
// 清空当前线程上下文与该事务相关的所有信息
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
// 钩子方法,子类实现
doCleanupAfterCompletion(status.getTransaction());
}
// 条件成立:说明该事务中有被挂起的事务
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
// 恢复被挂起的事务(把被挂起的事务重新绑定到当前线程上下文中)
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
此时就会去当前事务的TransactionStatus中查看一下SuspendedResourcesHolder是否存在,如果存在就说明又被挂起的事务,我们需要把这个事务进行恢复(因为事务提交了,之前的事务就需要被恢复)。具体进行事务恢复的逻辑在resume方法中
/**
* 恢复给定的事务
*
* @param transaction 当前线程上下文的事务对象
* @param resourcesHolder 要恢复的事务
* @see #doResume
* @see #suspend
*/
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
throws TransactionException {
if (resourcesHolder != null) {
// 获取被挂起的资源
Object suspendedResources = resourcesHolder.suspendedResources;
// 条件成立:被挂起的资源不为null
if (suspendedResources != null) {
// 执行具体的事务恢复逻辑,交由子类实现
doResume(transaction, suspendedResources);
}
// 把被挂起的事务的信息放到当前线程上下文中
List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
if (suspendedSynchronizations != null) {
TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
doResumeSynchronization(suspendedSynchronizations);
}
}
}
可以看到就是把之前被挂起的事务的信息以及事务同步器从SuspendedResourcesHolder对象中拿出来,然后重新放到线程上下文中,然后当被挂起的事务提交或回滚的时候,就可以从线程上下文中获取到它的事务同步器然后进行相应的方法回调了
事务同步器的生效条件
事务同步器并不是说总是能生效的,它是基于一个配置属性来判断是否能够生效,这个配置属性在事务管理器中可以进行设置:
private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
public final void setTransactionSynchronization(int transactionSynchronization) {
this.transactionSynchronization = transactionSynchronization;
}
这个属性有三个值可以进行选择:
/**
* 该事务同步模式表示事务同步始终生效
*
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER
*/
public static final int SYNCHRONIZATION_ALWAYS = 0;
/**
* 该事务同步模式表示事务同步仅在非“空”事务的时候生效
*
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
*/
public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
/**
* 该事务同步模式表示事务同步从不生效
*/
public static final int SYNCHRONIZATION_NEVER = 2;
这三个可选值的作用如下:
- SYNCHRONIZATION_ALWAYS
如果设置了该属性值,则不管什么事务传播级别,事务同步器的方法都能够进行相应的回调,这也是事务管理器的默认值
- SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
如果设置了该属性值,则只有非“空”事务中的事务同步器才能被回调,什么意思呢?对于比如PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER这三种事务传播级别来说,它们都表示不处于事务中,这样的话就相当于业务代码就不是在事务中执行了,所以也叫做是“空”事务,设置了SYNCHRONIZATION_ON_ACTUAL_TRANSACTION的话,对于“空”事务来说,尽管设置了事务同步器,也不会被回调。因为代码都不在事务中执行了,也就没有所谓的commit和rollback这些事务生命周期阶段了。应用场景可以是如果我们想要只针对监控真实的事务而不是这些“空”事务,那么我们就可以设置该属性值
- SYNCHRONIZATION_NEVER
如果设置了该属性,不管是真实事务还是“空”事务,都不会回调事务同步器