基于最新Spring 5.x,详细介绍了Spring事务源码,包括completeTransactionAfterThrowing尝试回滚事务、commitTransactionAfterReturning尝试提交事务,cleanupTransactionInfo清理事务信息等等方法。
此前的文章中,我们介绍了Spring AOP中最重要的类之一——TransactionInterceptor事务拦截器的invoke方法内部的createTransactionIfNecessary方法的源码:Spring 事务源码(6)—createTransactionIfNecessary处理事务属性并尝试创建事务【两万字】。下面我们接着学习后面的方法和逻辑,后面的代码包括事务的completeTransactionAfterThrowing提交、commitTransactionAfterReturning回滚、resume恢复等逻辑。
Spring 5.x 源码汇总
Spring 事务源码(1)—<tx:advice/>事务标签源码解析
Spring 事务源码(2)—<tx:annotation-driven/>事务标签源码解析
Spring 事务源码(3)—@EnableTransactionManagement事务注解源码解析
Spring 事务源码(4)—BeanFactoryTransactionAttributeSourceAdvisor注解事务通知器源码解析
Spring 事务源码(5)—TransactionInterceptor事务拦截器与事务的AOP增强实现
Spring 事务源码(6)—createTransactionIfNecessary处理事务属性并尝试创建事务【两万字】
Spring 事务源码(7)—事务的completeTransactionAfterThrowing回滚、commitTransactionAfterReturning提交以及事务源码总结【一万字】
在createTransactionIfNecessary
方法正常执行完毕之后,已经对于我们配置的事务属性进行了处理,可能已经开启了事务,或者加入到了此前的事务中(通过事务管理器的getTransation方法)。 随后将会调用proceedWithInvocation
方法,该方法就是正常的向后执行的逻辑。Spring AOP的各种增强的逻辑(通知)是采用基于拦截器链的责任链模式和方法的递归调用的形式来实现的(详见Spring AOP的源码部分)。
在invoke方法中调用invokeWithinTransaction方法时,第四个参数需要是一个InvocationCallback接口的实例,用于继续向后执行调用的逻辑。
/**
* TransactionAspectSupport内部的一个函数式接口
*
* 简单的回调接口,用于进行目标调用。应用于具体的拦截器/切面使其适应其AOP调用机制。
*/
@FunctionalInterface
protected interface InvocationCallback {
/**
* 执行调用的逻辑
*
* @return 执行返回的结果
* @throws Throwable 中途抛出的异常
*/
@Nullable
Object proceedWithInvocation() throws Throwable;
}
这个是一个函数式接口,因此可以使用Java8的新特性,而实际上,参数中传递的也是一个方法引用,这表示创递给invokeWithinTransaction方法的InvocationCallback参数对象在调用proceedWithInvocation方法时,实际上将会执行MethodInvocation#proceed方法的逻辑,这个方法我们在Spring AOP的源码中就见过了,该方法用于向后继续执行其他AOP增强的拦截器的逻辑,或者执行真正的目标方法(业务逻辑),最底层执行完毕之后还会倒序的执行返回的逻辑,比如后置通知。
在invocation.proceedWithInvocation
方法执行过程中,如果抛出了异常,那么将会执行completeTransactionAfterThrowing
方法,该方法同样会根据事务的配置信息,做出响应的处理,比如判断是否需要回滚事务,还是提交事务。
此前通过事务管理器的getTransaction
方法来获取事务,这里我们将见到另外两个非常重要的事务管理器的方法:rollBack-回滚事务和commit-提交事务。
需要注意的是,该方法执行完毕之后,仍然会向外层方法抛出当前异常,因此将可能导致外层事务回滚。
/**
* TransactionAspectSupport的方法
*
* 处理执行异常并完成事务。我们可能会提交或回滚,具体取决于配置。
*
* @param txInfo 有关当前事务的信息对象
* @param ex 抛出的异常
*/
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
//如果内部存在事务状态对象
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
/*
* 如果事务属性不为null,并且通过事物属性的rollbackOn方法判断出当前的异常需要进行回滚,那么就进行回滚
* 声明式事务一般都是这个逻辑
*/
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
/*
* 那么通过事务管理器执行rollback回滚操作
*/
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
} catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
} else {
/*
* 到这一步,表示不会执行回滚,那么通过事务管理器执行commit提交操作
* 但是如果TransactionStatus.isRollbackOnly()方法被设置为true,那么仍然会回滚
*/
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
} catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
通过配置的事务状态的rollbackOn方法来判断对于抛出的异常,当前事务是否需要进行回滚操作。
事务异常的回滚规则我们在此前就是很熟悉了,默认情况下只有RuntimeException或者Error类型的异常才会回滚,当前我们也可以配置自己的规则,如果是基于XML,那么就是
标签的rollback-for
和no-rollback-for
属性,如果是基于注解,那么就是@Transactional
注解rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName
属性。
Spring默认采用的TransactionAttribute类型为RuleBasedTransactionAttribute。RuleBasedTransactionAttribute继承并扩展了父类DefaultTransactionAttribute,主要就是重写了rollbackOn方法,提供了自定义哪些异常回滚、哪些异常不回滚的功能。
一个
标签就会被解析为一个RuleBasedTransactionAttribute
的实例,@Transactional
注解最终也会被解析为一个RuleBasedTransactionAttribute
,自定义回滚异常的特性就是通过rollback-for以及no-rollback-for属性来配置的。
这个方法的逻辑我们在此前解析< tx:advice/>标签时的RuleBasedTransactionAttribute的介绍部分就已经讲过了,在此不在赘述。
该方法根据事务的信息执行各种操作,比如回滚事务,或者设置rollbackOnly
属性为true,核心方法就是doRollback
和doSetRollbackOnly
。
/**
1. AbstractPlatformTransactionManager
2.
3. 回滚事务。核心方法是doRollback和doSetRollbackOnly。
*/
@Override
public final void rollback(TransactionStatus status) throws TransactionException {
//如果当前事务已完成,即已提交或者回滚,那么抛出异常
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
//处理回滚
processRollback(defStatus, false);
}
其内部调用的processRollback
是真正处理事务回滚的方法,该方法根据不同的情况做出不同的处理:
beforeCompletion
方法回调。保存点
,内层PROPAGATION_NESTED事务会开启保存点。如果具有保存点,那么就仅仅调用rollbackToHeldSavepoint
回滚保存点,但是我们在invokeWithinTransaction方法中知道,completeTransactionAfterThrowing
方法执行完毕仍然会抛出异常到外层事务中,因此仍然可能导致外层事务的回滚。如果是新开启的事务,那么就调用doRollback回滚该事物,该方法由具体的事务管理器子类来实现。
afterCompletion
方法回调。即使在执行beforeCompletion
回调、回滚保存点、回滚事务等过程中抛出RuntimeException或者Error异常,该回调仍然会执行。cleanupAfterCompletion
方法进行最后的清理工作,比如将当前事物状态设置为已完成,重置并释放连接(可能是放回连接池),此前绑定到当前线程的事务属性,恢复此前被挂起的资源和事务属性。/**
* AbstractPlatformTransactionManager的方法
*
* 处理实际的回滚。
*
* @param status 事务状态,代表着当前事务
* @throws TransactionException 如果回滚失败
*/
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
/*
* 1 在当前线程所有当前已注册的TransactionSynchronization上触发beforeCompletion方法回调
*/
triggerBeforeCompletion(status);
/*
* 2 判断是否具有保存点,内层PROPAGATION_NESTED事务会开启保存点
*/
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
/*
* 如果具有保存点,那么就仅仅回滚保存点
* 但是我们在invokeWithinTransaction方法中知道,completeTransactionAfterThrowing
* 方法执行完毕仍然会抛出异常到外层事务中,因此仍然可能导致外层事务的回滚
*/
status.rollbackToHeldSavepoint();
}
/*
* 3 判断是否是新开的事务或者是最外层事务,比如外层PROPAGATION_REQUIRED
* 外层PROPAGATION_NESTED以及PROPAGATION_REQUIRES_NEW
*/
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
/*
* 如果是新开启的事务,那么就回滚该事物
* 该方法由具体的事务管理器子类来实现
*/
doRollback(status);
}
/*
* 4 到这里,表示没有保存点,并且也不是最外层事务,而是当前事务方法参数到了外层事务中
* 比如内层PROPAGATION_REQUIRED、内层PROPAGATION_SUPPORTS、内层PROPAGATION_MANDATORY
*/
else {
// 如果status具有事务,那么这里表示外层的事务,这里就参与到外层事务的回滚操作中
if (status.hasTransaction()) {
/*
* 如果当前事务被设置为仅回滚,或者当前事务管理器的globalRollbackOnParticipationFailure属性为true,那么将事务设置为仅回滚
*
* globalRollbackOnParticipationFailure属性默认为true,表示只要你的参与事务失败了,就标记此事务为rollback-only
* 表示它只能给与回滚 而不能再commit或者正常结束了,也就是说,如果参与事务回滚,那么外部事物一定会回滚,即使内部的异常被catch了
*/
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
/*
* 将事务设置为仅回滚,即外层事务也将会回滚
* 该方法由具体的事务管理器子类来实现
*/
doSetRollbackOnly(status);
} else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
} else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// 返回在事务被全局标记为"仅回滚"的情况下是否尽早失败,即是否需要立即抛出异常
//一般为false,因此结果就是true,导致unexpectedRollback为false
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
} catch (RuntimeException | Error ex) {
//即使在执行beforeCompletion回调、回滚保存点、回滚事务等过程中抛出RuntimeException或者Error异常
//仍然会在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion方法回调
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
//抛出异常
throw ex;
}
/*
* 5 在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion方法回调
*/
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
//如果我们有一个仅全局回滚的标记,则引发UnexpectedRollbackException异常,一般来说不会抛出
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
} finally {
/*
* 6 完成后进行清理,必要时清除同步,然后调用doCleanupAfterCompletion。
*/
cleanupAfterCompletion(status);
}
}
在当前线程所有当前已注册的TransactionSynchronization上触发beforeCompletion
方法回调。
/**
* AbstractPlatformTransactionManager的方法
*
* 触发beforeCompletion回调。
*
* @param status 事务状态,代表着当前事务
*/
protected final void triggerBeforeCompletion(DefaultTransactionStatus status) {
//如果存在事务同步
if (status.isNewSynchronization()) {
if (status.isDebug()) {
logger.trace("Triggering beforeCompletion synchronization");
}
//在所有当前已注册的TransactionSynchronization上触发beforeCompletion回调
TransactionSynchronizationUtils.triggerBeforeCompletion();
}
}
/**
* TransactionSynchronizationUtils的方法
*
* 在所有当前已注册的TransactionSynchronization上触发beforeCompletion回调。
*/
public static void triggerBeforeCompletion() {
//遍历synchronizations中绑定到当前线程的TransactionSynchronization集合
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
try {
//依次调用beforeCompletion回调方法
synchronization.beforeCompletion();
} catch (Throwable tsex) {
logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex);
}
}
}
一般情况下,仅内层的PROPAGATION_NESTED事务方法会开启保存点,如果具有保存点,那么回滚到为事务保留的保存点,然后立即释放该保存点。
具体的原理还是很简单的,最终就是操作Connection#rollback
方法对该保存点进行回滚,并且操作Connection#releaseSavepoint
方法释放该保存点,之后还会将savepoint置为null。
即使这里仅仅回滚了保存点,但是我们在invokeWithinTransaction方法中知道,completeTransactionAfterThrowing方法执行完毕仍然会抛出异常到外层事务中,因此仍然可能导致外层事务的回滚。
/**
* AbstractTransactionStatus的方法
*
* 回滚到为事务保留的保存点,然后立即释放该保存点。
*
* 我们在invokeWithinTransaction方法中知道,completeTransactionAfterThrowing
* 方法执行完毕仍然会抛出异常到外层事务中,因此仍然可能导致外层事务的回滚
*/
public void rollbackToHeldSavepoint() throws TransactionException {
//获取保存点
Object savepoint = getSavepoint();
if (savepoint == null) {
throw new TransactionUsageException(
"Cannot roll back to savepoint - no savepoint associated with current transaction");
}
//使用保存点管理器来执行回滚保存点的操作,实际上SavepointManager就是当前的内部事务对象
//比如DataSourceTransactionManager的DataSourceTransactionObject
//DataSourceTransactionObject继承了JdbcTransactionObjectSupport
//回滚操作
getSavepointManager().rollbackToSavepoint(savepoint);
//释放保存点
getSavepointManager().releaseSavepoint(savepoint);
//设置保存点为null
setSavepoint(null);
}
/**
* JdbcTransactionObjectSupport的方法
*
* 回滚到给定的JDBC 3.0保存点。
*/
@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
//获取连接持有者
ConnectionHolder conHolder = getConnectionHolderForSavepoint();
try {
//获取连接Connection并调用rollback方法参数为当前的保存点
conHolder.getConnection().rollback((Savepoint) savepoint);
//重置当前连接的rollbackOnly为false
conHolder.resetRollbackOnly();
} catch (Throwable ex) {
//回滚失败抛出异常
throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
}
}
/**
* JdbcTransactionObjectSupport的方法
*
* 释放给定的JDBC 3.0保存点。
*/
@Override
public void releaseSavepoint(Object savepoint) throws TransactionException {
//获取连接持有者
ConnectionHolder conHolder = getConnectionHolderForSavepoint();
try {
//获取连接Connection并调用releaseSavepoint方法参数为当前的保存点
conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);
} catch (Throwable ex) {
logger.debug("Could not explicitly release JDBC savepoint", ex);
}
}
如果是新开的事务或者是最外层事务方法,比如外层PROPAGATION_REQUIRED,外层PROPAGATION_NESTED以及PROPAGATION_REQUIRES_NEW,那么在抛出异常之后将会真正的回滚该事务,该方法由具体的事务管理器子类来实现。
DataSourceTransactionManager的实现很简单,就是调用内部的连接的Connection#rollback
方法执行回滚操作。
/**
* DataSourceTransactionManager的方法
*
* @param status 事务状态,代表当前事务
*/
@Override
protected void doRollback(DefaultTransactionStatus status) {
//获取内部事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
//获取内部的连接
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
//很简单,调用Connection的rollback方法执行回滚
con.rollback();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
如果没有保存点,并且也不是最外层事务或者新开的事务,那么一定是当前事务方法参数到了外层事务中,比如内层PROPAGATION_REQUIRED,内层PROPAGATION_SUPPORTS,内层PROPAGATION_MANDATORY,这表示该事务方法和外层的事务方法属于同一个事务。
将会调用doSetRollbackOnly
方法设置给定的事务仅回滚,即这个事务一定会回滚
!注意这里的设置的不再是底层的Connection的属性,而是设置的ConnectionHolder的rollbackOnly属性为true。
/**
* DataSourceTransactionManager的方法
*
* 设置给定的事务仅回滚。仅当当前事务参与现有事务时才回滚调用。
*
* @param status 现有的事务
*/
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
//设置为仅回滚,就是设置ResourceHolderSupport的rollbackOnly属性为true,外层事务也将必定回滚
txObject.setRollbackOnly();
}
/**
* 设置ConnectionHolder的rollbackOnly属性为true
*/
public void setRollbackOnly() {
getConnectionHolder().setRollbackOnly();
}
在上面的方法正常处理事务完毕之后,或者说在执行beforeCompletion
方法回调、回滚保存点、回滚事务等过程中,如果发生了RuntimeException或者Error级别的之后,都将会调用triggerAfterCompletion
方法,该方法会在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion
方法回调。
/**
* AbstractPlatformTransactionManager的方法
*
* 触发afterCompletion回调。
*
* @param status 事务状态,代表着当前事务
* @param completionStatus TransactionSynchronization常量表示的事务完成状态
* 如果回滚处理成功则是STATUS_ROLLED_BACK,回滚过程中抛出异常则是STATUS_UNKNOWN
*/
private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
//如果是新事务同步
if (status.isNewSynchronization()) {
//获取目前注册的TransactionSynchronization集合
List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
//清空当前线程绑定的TransactionSynchronization集合
TransactionSynchronizationManager.clearSynchronization();
//如果没有事务或者是新事物
if (!status.hasTransaction() || status.isNewTransaction()) {
if (status.isDebug()) {
logger.trace("Triggering afterCompletion synchronization");
}
//立即调用afterCompletion回调
invokeAfterCompletion(synchronizations, completionStatus);
} else if (!synchronizations.isEmpty()) {
//否则表示有事务但不是新事务
//我们参与的现有事务在此Spring事务管理器的范围之外进行了控制,
//那么尝试向现有(JTA)事务注册一个afterCompletion回调。
registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
}
}
}
/**
* AbstractPlatformTransactionManager的方法
*
* @param synchronizations 当前事务同步
* @param completionStatus 事务执行结果
*/
protected final void invokeAfterCompletion(List<TransactionSynchronization> synchronizations, int completionStatus) {
TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus);
}
public static void invokeAfterCompletion(@Nullable List<TransactionSynchronization> synchronizations,
int completionStatus) {
if (synchronizations != null) {
//遍历
for (TransactionSynchronization synchronization : synchronizations) {
try {
//依次调用afterCompletion方法
synchronization.afterCompletion(completionStatus);
} catch (Throwable tsex) {
logger.error("TransactionSynchronization.afterCompletion threw exception", tsex);
}
}
}
}
上面的步骤执行完毕之后,在finally块中一定会执行cleanupAfterCompletion方法,该方法主要做了以下几件事:
/**
* AbstractPlatformTransactionManager的方法
*
* 完成后进行清理,必要时清除同步,然后调用doCleanupAfterCompletion。
*
* @param status 事务状态,代表着当前事务
*/
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
/*
* 1 设置当前事务状态为已完成,即completed属性为true
*/
status.setCompleted();
/*
* 2 如果是新同步,那么这里清除绑定到当前线程的事务信息
* 比如事务同步、事务名、事务只读状态、事务隔离级别、事务激活状态
*/
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
/*
* 3 如果是新事务,那么这里调用doCleanupAfterCompletion模版方法
* 该方法同样是由具体的子类实现的,用于扩展自己的行为。
*/
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
/*
* 4 如果存在此前已经被挂起的事务资源,那么这里需要恢复此前的资源
*/
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
//获取当前的内部事务对象,比如对于DataSourceTransactionManager事务管理器
//他创建的内部事务对象就是DataSourceTransactionObject
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
/*
* 唤醒挂起的事务资源
* 重新绑定之前挂起的数据库资源,重新唤醒并注册此前的同步器,重新绑定各种事务信息
*/
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
/**
* TransactionSynchronizationManager的方法
*
* 清除当前线程的整个事务同步状态:已注册的同步以及各种事务特征。
*/
public static void clear() {
//清除事务同步
synchronizations.remove();
//清除事务名
currentTransactionName.remove();
//清除事务只读状态
currentTransactionReadOnly.remove();
//清除事务隔离级别
currentTransactionIsolationLevel.remove();
//清除事务有效状态
actualTransactionActive.remove();
}
该方法是一个模版方法,由具体的事务管理器子类自己实现,主要用于内部事务对象的清理操作。
DataSourceTransactionManager实现的方法如下,主要就是重置Connection连接的属性,基于释放连接,这里会根据根据DataSource以及Collection的具体的实现做出不同的操作,比如对于DruidPooledConnection
,它的close方法就被重写,就可能实现连接的回收利用而不是真正的释放。最终还会清除当前连接持有者的属性,比如transactionActive重置为false,rollbackOnly置为false等等。
/**
* DataSourceTransactionManager实现的方法
*
* @param transaction 当前内部事务对象
*/
@Override
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 如果是新获取的连接
if (txObject.isNewConnectionHolder()) {
//那么将绑定的当前线程的指定key的连接资源解绑
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
//重置连接
//获取连接
Connection con = txObject.getConnectionHolder().getConnection();
try {
//重置连接属性为自动提交
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
//在事务处理后重置给定的Connection的只读标志和隔离级别属性
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
} catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
//如果是新获取的连接
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
}
//释放连接,根据DataSource以及Collection的具体的实现做出不同的操作
//比如对于DruidPooledConnection,它的close方法就被重写,就可能实现连接的回收利用而不是真正的释放
DataSourceUtils.releaseConnection(con, this.dataSource);
}
//清除当前连接持有者的属性,比如transactionActive重置为false,rollbackOnly置为false等等
txObject.getConnectionHolder().clear();
}
如果存在此前已经被挂起的事务资源,那么这里需要恢复此前的资源,那么将会调用该方法唤醒挂起的事务资源:重新绑定之前挂起的数据库资源,重新唤醒并注册此前的同步器,重新绑定各种事务信息。
怎么恢复此前事务呢?实际上,我们只要了解了“挂起”
的概念,那么“恢复”
的概念就很简单了,那么什么是“挂起”事务呢?难道真的是对一个连接做了什么操作?实际上并没有什么操作,而且原理真的非常简单。根据此前的源码,首先,我们需要明白,我们每次都只能操作当前绑定的连接对象,操作的连接都是绑定到resources的线程本地变量。
我们所说的“挂起”事务,实际上就是将绑定到当前线程的连接对象替换为一个新的连接对象,并且被替换的连接对象保存起来,所以,此后都是操作一个新的连接对象,因为一个连接对应一个事务,那么肯定是一个新的事务了,但这在外人看起来,此前的连接和事务就被像挂起了一样,此前的事务并没有被提交或者回滚完成!
在我们了解了“挂起”的概念之后,那么“恢复”的概念就好知道了,由于此前保存了被“挂起”的资源suspendedResources,那么在当前事务或者方法完成之后,我们直接将保存的连接资源再次绑定为当前线程正在使用资源即可,也就是绑定到resources线程本地变量。
在我们学习了源码之后,事务的“挂起”和“恢复”是不是突然觉得很简单?
/**
* AbstractPlatformTransactionManager的方法
*
* 恢复给定的事务。首先委托doResume模板方法,然后恢复事务同步。
*
* @param transaction 当前事务对象
* @param resourcesHolder 此前被挂起的资源
*/
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
throws TransactionException {
if (resourcesHolder != null) {
//被挂起的事务资源
Object suspendedResources = resourcesHolder.suspendedResources;
if (suspendedResources != null) {
//首先调用doResume恢复当前事务的资源。事务同步将在此后恢复。
//该方法作为模版方法,由子类来实现
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);
}
}
}
/**
* DataSourceTransactionManager的方法
*
* @param transaction 当前事务,DataSourceTransactionManager的实现中,该属性没有用到
* @param suspendedResources 被挂起的资源
*/
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
//重新将此前挂起的事务以当前数据源为key绑定到当前线程的事务,bindResource方法我们此前就见过了
//这就表示"激活"了这个挂起的事务,是不是很简单?
TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}
清除当前绑定的事务信息、恢复老的事务信息绑定。
每一个当前的TransactionInfo
都通过oldTransactionInfo
属性保留了对前一个TransactionInfo的引用,这里直接获取前一个TransactionInfo并绑定到transactionInfoHolder线程本地变量中。
/**
* TransactionAspectSupport
*
* 重置TransactionInfo的ThreadLocal
* 无论是方法正常还是异常完成,都会调用该方法
*/
protected void cleanupTransactionInfo(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
if (txInfo != null) {
//使用堆栈还原旧的事务TransactionInfo。如果未设置,则为null。
txInfo.restoreThreadLocalStatus();
}
}
/**
* TransactionInfo的方法
*/
private void restoreThreadLocalStatus() {
//使用堆栈还原旧的事务TransactionInfo。
//每一个当前的TransactionInfo都通过oldTransactionInfo属性保留了对前一个TransactionInfo的引用
transactionInfoHolder.set(this.oldTransactionInfo);
}
目标方法成功执行之后,返回结果之前调用该方法提交事务。通过事务管理器执行commit提交事务操作,但是如果TransactionStatus.isRollbackOnly()
方法返回true,那么仍然会回滚。
/**
1. TransactionAspectSupport的方法的方法
2.
3. 在成功完成方法调用之后执行。如果我们不创建事务,则不执行任何操作。
4. 5. @param txInfo information about the current transaction
*/
protected void commitTransactionAfterReturning(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
//通过事务管理器执行commit提交事务操作
//但是如果TransactionStatus.isRollbackOnly()方法被设置为true,那么仍然会回滚
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
核心就是事务管理器的commit
方法!
仅回滚
(一般是在当前方法中抛出异常并手动处理),那么调用processRollback
执行回滚。全局回滚
(这种一般就是因为对于内层方法设置了PROPAGATION_SUPPORTS或者内层PROPAGATION_REQUIRED或者内层PROPAGATION_MANDATORY的传播行为),并且还抛出了异常,但是在外层方法中被捕获了的情况,此时仍然会执行事务回滚,并且还会在处理最外层事务方法时抛出UnexpectedRollbackException
异常,来提醒开发者所有的外部和内部操作都已被回滚!processCommit
执行真正的事务提交,这是核心方法。/**
1. AbstractPlatformTransactionManager的骨干实现
2.
3. 尝试提交事务,但仍可能会回滚
4. 内部委托给isRollbackOnly,doCommit和rollback方法
*/
@Override
public final void commit(TransactionStatus status) throws TransactionException {
//如果事务已完成,则抛出异常
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
//获取当前事务状态
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
/*
* 1 如果事务明确被设置为仅回滚,那么执行回滚
*/
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
//处理回滚,该方法此前就见过了
processRollback(defStatus, false);
return;
}
/*
* shouldCommitOnGlobalRollbackOnly方法用于判断返回是否以全局方式对已标记为仅回滚的事务调用doCommit提交,
* 默认实现返回false,即不会提交,而是一起回滚,但是JtaTransactionManager重写返回true
* 并且如果当前事务被设置为全局回滚,对于DataSourceTransactionObject来说就是判断内部的ConnectionHolder的rollbackOnly属性
*
* 2 以上条件都返回true,那么表示会执行回滚
*/
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
/*
* 处理回滚,该方法此前就见过了。注意这里的unexpected参数为true
* 对于加入到外层事务的行为,如果在内层方法中进行了回滚,即使异常被捕获,由于被设置为了仅回滚,那么该事物的所有操作仍然会回滚
* 并且还会在处理最外层事务方法时抛出UnexpectedRollbackException异常,来提醒开发者所有的外部和内部操作都已被回滚!
*
* 这一般对于内层PROPAGATION_SUPPORTS或者内层PROPAGATION_REQUIRED或者内层PROPAGATION_MANDATORY生效,
* 对于内层PROPAGATION_NOT_SUPPORTED则无效。
*/
processRollback(defStatus, true);
return;
}
/*
* 3 最后才会真正的提交
*/
processCommit(defStatus);
}
处理实际的提交,Rollback-only标志已被检查和应用。大概逻辑为:
beforeCompletion
方法回调;具有保存点
,内层PROPAGATION_NESTED事务会开启保存点。如果具有保存点,因为保存点内部的代码正常执行完毕,那么就释放保存点,但是并不会提交事务,而是需要等待外层事务方法去提交。
新开的事务或者是最外层事务
,比如外层PROPAGATION_REQUIRED、外层PROPAGATION_NESTED以及PROPAGATION_REQUIRES_NEW。如果是新开启的事务,那么就调用doCommit真正的提交该事物,该方法由具体的事务管理器子类来实现。
没有保存点,并且也不是最外层事务或者新开的事务
,而是当前事务方法参数到了外层事务中,比如内层PROPAGATION_REQUIRED、内层PROPAGATION_SUPPORTS、内层PROPAGATION_MANDATORY,这表示内外层方法处于同一个事务中,此时什么都不会做,仅仅检查rollbackOnly标志。
有rollbackOnly标志,那么抛出UnexpectedRollbackException异常。
afterCompletion
方法回调,并且还会将异常抛出。afterCommit
回调,随后在finally中执行最终触发afterCompletion
方法回调。cleanupAfterCompletion
方法进行最后的清理工作,比如将当前事物状态设置为已完成,重置并释放连接(可能是放回连接池),此前绑定到当前线程的事务属性,恢复此前被挂起的资源和事务属性。
/**
* AbstractPlatformTransactionManager的方法
*
* 处理实际的提交。Rollback-only标志已被检查和应用。
*
* @param status 代表当前事务对象
*/
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
//beforeCompletion方法已回调的标志位
boolean beforeCompletionInvoked = false;
try {
//意外的回滚标志
boolean unexpectedRollback = false;
/*
* 1 准备提交,要在beforeCommit同步回调发生之前执行
* 该方法为空实现,留给子类重写
*/
prepareForCommit(status);
/*
* 2 触发beforeCommit回调。
* 在当前线程所有当前已注册的TransactionSynchronization上触发beforeCommit方法回调
*/
triggerBeforeCommit(status);
/*
* 3 触发beforeCompletion回调。
* 在当前线程所有当前已注册的TransactionSynchronization上触发beforeCompletion方法回调
*/
triggerBeforeCompletion(status);
//标志位改为true
beforeCompletionInvoked = true;
/*
* 2 判断是否具有保存点,内层PROPAGATION_NESTED事务会开启保存点,即嵌套事务
*/
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
/*
* 如果具有保存点,因为保存点内部的代码正常执行完毕,那么就释放保存点
* 但是并不会提交事务,而是需要等待外层事务方法去提交
*/
status.releaseHeldSavepoint();
}
/*
* 3 判断是否是新开的事务或者是最外层事务,比如外层PROPAGATION_REQUIRED
* 外层PROPAGATION_NESTED以及PROPAGATION_REQUIRES_NEW
*/
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
/*
* 如果是新开启的事务或者最外层事务,那么就提交该事物
* 该方法由具体的事务管理器子类来实现,真正的实现事务的提交
*/
doCommit(status);
}
// 返回在事务被全局标记为"仅回滚"的情况下是否尽早失败,即是否需要立即抛出异常
//一般为false,因此结果就是true,导致unexpectedRollback为false
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
//如果我们有一个全局rollback-only的标记,但仍未从提交中获得相应的异常,
//则抛出UnexpectedRollbackException,但是此时事务已经被提交了
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
/*处理各种异常*/ catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
//如果在执行以上方法过程中抛出UnexpectedRollbackException异常
//那么在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion方法回调
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
} catch (TransactionException ex) {
//如果在执行以上方法过程中抛出TransactionException异常
//返回是否在doCommit调用失败时执行doRollback,一般为false,即不会
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
} else {
//在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion方法回调
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
} catch (RuntimeException | Error ex) {
//如果在执行beforeCompletion回调、回滚保存点、回滚事务等过程中抛出RuntimeException或者Error异常
//那么在当前线程所有当前已注册的TransactionSynchronization上触发afterCompletion方法回调
//如果还没有触发beforeCompletion方法回调
if (!beforeCompletionInvoked) {
//触发beforeCompletion回调。
triggerBeforeCompletion(status);
}
//调用doRollback方法处理提交事务时的异常,随后触发afterCompletion方法回调
doRollbackOnCommitException(status, ex);
throw ex;
}
/*
* 4 事务成功提交后调用,触发afterCommit回调。
* 在当前线程所有当前已注册的TransactionSynchronization上触发afterCommit方法回调
*/
try {
triggerAfterCommit(status);
} finally {
//最终触发afterCompletion方法回调
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
} finally {
/*
* 6 完成后进行清理,必要时清除同步,然后调用doCleanupAfterCompletion。
*/
cleanupAfterCompletion(status);
}
}
内层PROPAGATION_NESTED事务会开启保存点,即“嵌套事务”。如果存在保存点,那么就释放保存点,因此内部的代码正常执行完毕,但是并不会提交事务,而是需要等待外层事务方法去最后提交。
/**
* AbstractTransactionStatus的方法
*
* 释放为该事务保留的保存点,因为此保存点中的代码已正常执行完毕
*/
public void releaseHeldSavepoint() throws TransactionException {
//获取保存点
Object savepoint = getSavepoint();
if (savepoint == null) {
throw new TransactionUsageException(
"Cannot release savepoint - no savepoint associated with current transaction");
}
//释放保存点,该方法我们此前就讲过了
//实际上就是获取连接Connection并调用releaseSavepoint方法参数为当前的保存点
getSavepointManager().releaseSavepoint(savepoint);
//将保存点设置为null
setSavepoint(null);
}
如果是新开启的事务或者最外层事务,那么由于事物内部的代码全部成功执行,那么这里就提交该事物,该方法由具体的事务管理器子类来实现。
DataSourceTransactionManager的实现很简单,就是调用内部的连接的Connection#commit
方法执行提交操作。
/**
1. DataSourceTransactionManager的方法
2.
3. 真正的提交事务
4. 5. @param status 当前事务对象
*/
@Override
protected void doCommit(DefaultTransactionStatus status) {
//获取内部事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
//获取内部的连接
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
//很简单,调用Connection的commit方法执行提交
con.commit();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
经过我们上面几篇文章的源码分析,我们已经对Spring声明式事物的整体流程有一个全面的掌握,下面总结一些比较重要的点,实际上还有很多细节等待着我们去看源码:
Spring AOP
的逻辑来实现的,可以说是Spring AOP的最佳实践之一,还有一个比较好的实践就是声明式的Spring Cache。事物的传播行为
则不会,该属性用于规定在方法的互相调用的时候事物是怎么被应用的,这是Spring提供的特性,将会在应用Connection连接属性之前被Spring的代码解析并应用不同的处理流程,最终并没有对应Connection连接的某个具体属性或者方法。PROPAGATION_NESTED
方法的传播行为通常被称为会开启“嵌套”事务,然而,这个“嵌套事务”实际上是通过SavePoint保存点来实现
的,这个保存点属于当前已存在的外部事物,所以说仍然只有一个物理事物,这就是真正的“嵌套事务”的实现,仅仅是保存点而已。
PROPAGATION_REQUIRES_NEW
,此时将会获取一个新的连接,开启新的事务。ThreadLocal
线程本地变量。
Spring声明式事务对于同一个线程在同一个时刻只能使用(激活)一个资源(比如连接),当前绑定的资源对象(绑定到resources的线程本地变量中),就是正在使用的资源,并且,通常情况下一个连接只能对应(开启)一个物理事务,因此如果是内层REQUIRES_NEW事务方法,为了开启新的事务,将会获取一个新的连接
。“挂起”
事务,实际上就是将绑定到当前线程的连接对象从线程本地变量中移除,并且被移除的连接对象将会被保存起来(当前的TransactionStatus通过suspendedResources属性保存了此前被挂起的一系列资源,而被挂起的资源中又保存了更久之前被挂起的资源,这样就通过引用保存的方式形成了一个挂起资源链),随后,对于当前的事务方法,将可能获取并绑定一个新的连接来开启新的事务(比如REQUIRES_NEW),或者不会开启事务(比如NOT_SUPPORTED)。但是由于此前的连接(事务)在当前内层事务方法执行其间并不会被被进行任何操作执行提交或者回滚,这在业务开发人员人看起来,此前的连接和事务就被像“挂起”——暂停了一样,虽然此前获取的连接和事务虽然仅仅被搁置在一旁,并没有什么真正的执行什么“挂起”操作。“恢复”
就很简单了,由于我们保存了被“挂起”的资源suspendedResources,那么在当前事务或者方法完成之后,我们直接将保存的连接资源再次绑定为当前线程正在使用资源即可,也就是绑定到resources线程本地变量,后续的事务操作,比如提交和回滚就会又执行我们重新绑定的连接对应的事务了。getTransaction、suspend、startTransaction、rollback、commit
等等,其骨干实现位于抽象实现类AbstractPlatformTransactionManager
中。而AbstractPlatformTransactionManager基于模版方法模式
,它提供了这些方法的流程的骨干实现,并且提供了一系列的以do开头的模版方法供具体的事务管理器子类来实现自己的逻辑,这样的设计思路值得学习。 Spring声明式事务只能用于方法级别而不能作用于代码片段级别,但是配置简单使用方便,而Spring也提供了编程式事务,它的作用范围更加细分,可以精确到一个方法中的某些代码片段,但是使用起来稍微麻烦,并且夫业务代码紧紧耦合。一般的项目中,使用声明式事物即可实现事务控制的要求,因此编程式事务用的并不多,此前我们学习了声明式事物的源码,现在我们来简单看看编程式事务的源码,有了此前的基础,编程式事物的源码将会非常简单!
Spring的编程式事务,我们最常用的就是TransactionTemplate,该模板类中包含了一个TransactionManager的实例,对于TransactionManager的方法进行了一系列的封装,提供了非常方便的编程式事务的执行方法,这一点它和其他template类是一样的。
TransactionTemplate的uml类图如下:
TransactionTemplate实现了TransactionDefinition接口,因此,我们可以配置使用该模版来管理的事物的各种属性,比如隔离级别、传播行为、超时时间等等。
TransactionTemplate实现了InitializingBean接口,在被初始化之后会执行afterPropertiesSet回调方法,该方法被用于检测内部的transactionManager是否存在,不存在则会抛出异常:
@Overridepublic void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
TransactionTemplate实现了TransactionOperations接口,该接口提供了一系列方法用于快捷的执行事务性操作,最常见的比如execute方法。该方法中将会自动执行开启事务、会滚或者提交事务的逻辑。
/**
* TransactionTemplate的内部的事务管理器实例
*/
@Nullable
private PlatformTransactionManager transactionManager;
/**
* TransactionTemplate的方法
*
* 在事务中执行由给定的回调对象指定的操作,因此这个回调参数对象的方法一般就是业务逻辑
*
* @param action 回调对象
* @param 返回类型
* @return 回调返回值
*/
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
} else {
//调用getTransaction方法获取事务,该方法我们在此前就讲过了
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//调用回调函数的方法,该方法一般就是业务逻辑代码的执行
result = action.doInTransaction(status);
} catch (RuntimeException | Error ex) {
//业务代码引发RuntimeException或者Error,那么执行回滚
rollbackOnException(status, ex);
throw ex;
} catch (Throwable ex) {
//抛出的其他异常,同样执行回滚
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
//执行成功,提交事务,该方法我们在此前就讲过了
this.transactionManager.commit(status);
//返回结果
return result;
}
}
/**
* TransactionTemplate的方法
*
* 执行回滚,正确处理回滚异常。
*
* @param status 代表事务的对象
* @param ex 引发的应用程序异常或错误
* @throws TransactionException 如果发生回滚错误
*/
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
logger.debug("Initiating transaction rollback on application exception", ex);
try {
//调用rollback方法回滚事务,该方法我们在此前就讲过了
this.transactionManager.rollback(status);
} catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
} catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
可以看到,方案参数传递一个TransactionCallback接口实例,这是一个函数式接口,传递参数时可以使用lambda表达式,也可以使用匿名对象,实际上就是传递需要在事务上下文中运行的业务代码。
内部的源码就是调用事务管理器的getTransaction、rollback、commit
这三个模版方法,很简单,这些方法的源码我们在此前就讲过了。
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!