作为一名认知有限的中国人,我对年的喜爱,胜过其他一切,因为它给了我拒绝一切的合理理由。每到这个时候,我都会用各种理由来为自己的不作为开脱,今年亦是如此。看着频频发出警报的假期余额,我内心的焦躁变得更加强烈。为了抚慰这烦人的情绪,我决定让自己静下来,继续梳理工作经常用到的Spring事务。通过前面三篇文章,我知道了事务的配置流程,也懂得了向Spring容器中注册事务的流程,更了解了Spring事务中的相关组件及其作用,但这依旧无法让我认识到这个知识点的全貌,所以希望通过今天的跟踪能完全了解Spring事务;也希望通过这次跟踪对Spring事务进行一次总结;更希望通过这次跟踪结束本系列,以为后面的学习腾出时间。
这里想跟大家说声对不起!在增加这段文字之前,下述描述中有这样一段:2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象,具体如下图所示:
但根据实际的接口定义,getTransaction()方法接收的参数的类型为TransactionDefinition,而非TransactionAttribute,所以本次修改将更改这个错误!不过这里这么些也没有问题,因为TransactionAttribute继承了TransactionDefinition接口。关于这两个接口之间的继承关系,如果觉得本博客梳理的可以的同仁可以翻看一下《Spring 事务原理总结三》这篇文章。
回到第一篇文章中的案例(注意这个案例并没有真正操作数据库中的数据),在transferService.check("jack", "tom", BigDecimal.valueOf(100));这行代码处新增一个断点,然后运行代码,会看到下图所示的情况:
继续执行,会看到代码执行到了TransactionInterceptor#invoke(MethodInvocation invocation)方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码),如下图所示:
这里看到的参数MethodInvocation的实际类型为org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation,其中包含了许多信息,具体如下图所示:
接下来继续执行,会进入到TransactionInterceptor#invokeWithinTransaction (Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码,今天我们会详细看一下这段代码的具体逻辑),先来看一下这段逻辑的具体代码:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);boolean hasSuspendingFlowReturnType = isSuspendingFunction &&COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {throw new IllegalStateException("Coroutines invocation not supported: " + method);}CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {Class<?> reactiveType =(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +method.getReturnType());}return new ReactiveTransactionSupport(adapter);});InvocationCallback callback = invocation;if (corInv != null) {callback = () -> KotlinDelegate.invokeSuspendingFunction(method, corInv);}return txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, rtm);}PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && txAttr != null) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null) {if (retVal instanceof Future<?> future && future.isDone()) {try {future.get();}catch (ExecutionException ex) {if (txAttr.rollbackOn(ex.getCause())) {status.setRollbackOnly();}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}}else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}}commitTransactionAfterReturning(txInfo);return retVal;}else {Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = cpptm.execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException runtimeException) {throw runtimeException;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}
}
这个方法的第一行会首先获取一个TransactionAttributeSource对象(实际类型为AnnotationTransactionAttributeSrouce。这里多啰嗦几句,这个类是Spring中处理事务管理的一个类,它负责在基于注解的事务控制机制中解析方法级别的事务属性。在使用声明式事务管理时,比如通过@Transactional注解,其作用至关重要。具体来讲,当Spring发现一个类的方法上标注了@Transactional注解时,AnnotationTransactionAttributeSrouce会在这个类加载和初始化过程中被Spring AOP使用来解析该注解,并根据注解中的属性,如传播行为、隔离级别、回滚规则等,生成相应的事务属性对象,通常是TransactionAttribute的实例或其子类。这样当执行到被注解的方法时,Spring的事务代理能够根据这些属性来正确的管理和控制事务的声明周期,确保方法内数据库操作的原子性和一致性。),具体如下图所示:
接下来会从拿到的TransactionAttributeSource对象中拿到一个TransactionAttribute类型的对象,该对象中包含了许多数据,譬如事务超时时间、事务隔离级别、事务可读属性、事务传播行为等等,具体如下图所示:
接下来继续执行,会执行到第三行,这里的主要作用就是从Spring容器中拿到事务管理器对象(拿对象的详细过程可以研读determineTransactionManager()方法的代码,我们拿到的这个对象的实际类型为JdbcTransactionManager,关于该类的继承结构可以参考前一篇文章,即《Spring 事务原理总结三》),具体如下图所示:
接下来继续执行,会发现先跳过if分支,然后执行PlatformTransactionManager ptm = asPlatformTransactionManager(tm);这段代码,具体如下图所示:
关于asPlatformTransactionManager()方法的源码如下所示(这个方法的主要作用是判断当前的tm对象是否是PlatformTransactionManager类型,如果是则返回当前对象,如果不是则直接抛出参数非法异常——IllegalStateException)):
private PlatformTransactionManager asPlatformTransactionManager(@Nullable Object transactionManager) {if (transactionManager == null) {return null;}if (transactionManager instanceof PlatformTransactionManager ptm) {return ptm;}else {throw new IllegalStateException("Specified transaction manager is not a PlatformTransactionManager: " + transactionManager);}
}
继续执行,会到final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);这一行,个人理解这行代码的主要作用是获取当前正在执行的目标方法的签名,比如这里的org.com.chinasofti.springtransaction.service.TransferServiceImpl.check(这是我们自定义的check()方法,该方法上添加了@Transactional注解),详情参见下图:
接下来一起看一下if分支的判断逻辑,详细代码为:if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)),这里首先会判断txAttr(它是一个TransactionAttribute类型的对象)是否为空,因为前面获取过,所以这里等于null的判断结果是false;再来看一下后半段,ptm是一个PlatformTransactionManager类型的对象,虽然这里的CallbackPreferringPlatformTransactionManager继承了PlatformTransactionManager接口,但是这里的实际类型JdbcTransactionManager并没有实现CallbackPreferringPlatformTransactionManager接口,所以后半段逻辑最终返回的结果是true,具体如下图所示:
下面详细看一下if分支中的第一句代码TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);,从字面大概可以猜出这段代码的主要作用就是创建一个TransactionInfo对象,下面来看一下这里涉及的两个方法的代码:
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}/*** Prepare a TransactionInfo for the given attribute and status object.* @param txAttr the TransactionAttribute (may be {@code null})* @param joinpointIdentification the fully qualified method name* (used for monitoring and logging purposes)* @param status the TransactionStatus for the current transaction* @return the prepared TransactionInfo object*/
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {// We need a transaction for this method...if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}// The transaction manager will flag an error if an incompatible tx already exists.txInfo.newTransactionStatus(status);}else {// The TransactionInfo.hasTransaction() method will return false. We created it only// to preserve the integrity of the ThreadLocal stack maintained in this class.if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}// We always bind the TransactionInfo to the thread, even if we didn't create// a new transaction here. This guarantees that the TransactionInfo stack// will be managed correctly even if no transaction was created by this aspect.txInfo.bindToThread();return txInfo;
}
通过阅读createTransactionIfNecessary()方法的源码不难发现,这个方法主要做了这样几件事情:1.将TransactionAttribute对象包装为DelegatingTransactionAttribute对象(重写该对象的getName()方法,返回org.com.chinasofti.springtransaction.service.TransferServiceImpl.check);2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionDefinition对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象;3.调用本类(TransactionAspectSupport)中的prepareTransactionInfo()方法(这个方法就是上面罗列的第二个方法的源码)。接下来看一下prepareTransactionInfo()方法的执行逻辑:1.创建TransactionInfo对象,该对象会持有TransactionAttribute、PlatformTransactionManager及目标方法签名;2.调用TransactionInfo对象上的newTransactionStatus()方法(该方法会接收一个TransactionStatus对象。该方法的主要目的就是将TransactionStatus对象赋值给TransactionInfo对象,也就是说该方法的主要逻辑就是赋值);3.调用TransactionInfo对象上的bindToThread()方法,将TransactionInfo对象绑定到当前线程上,该方法的源码如下所示:
private void bindToThread() {// Expose current TransactionStatus, preserving any existing TransactionStatus// for restoration after this transaction is complete.this.oldTransactionInfo = transactionInfoHolder.get();transactionInfoHolder.set(this);
}
注意:这里看到的源码(bindToThread()、createTransactionIfNecessary()、prepareTransactionInfo()包括TransactionInfo类)都位于TransactionAspectSupport类中。
接下来继续回到TransactionAspectSupport类的invokeWithinTransaction()方法中,直接来到cleanupTransactionInfo(txInfo)这段代码处,最终会调到TransactionInfo对象上的restoreThreadLocalStatus()方法,该方法源码为:
private void restoreThreadLocalStatus() {// Use stack to restore old transaction TransactionInfo.// Will be null if none was set.transactionInfoHolder.set(this.oldTransactionInfo);
}
这里又看到了transactionInfoHolder对象,这个对象是在TransactionAspectSupport对象中创建的,其实际类型就是NamedThreadLocal,说白了就是一个ThreadLocal(关于这个类的解释这里不再赘述,后面会专门写一篇文章对其进行介绍)。这里想在啰嗦几句,还记得前面创建TransactionInfo对象的代码吗?那里的主要目的就是想transactionInfoHolder对象中存放TransactionInfo对象(实际上就是操作TransactionAspectSupport对象中transactionInfoHolder对象)。
接下来继续向下走,会来到提交事务的代码处(commitTransactionAfterReturning(txInfo)),详细执行逻辑如下所示:
这个被调用的方法(commitTransactionAfterReturning(@Nullable TransactionInfo txInfo))的源码如下所示:
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");}txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}
}
从源码不难看出,事务提交的主要逻辑就是从TransactionInfo对象中拿到TransactionManager对象然后调用其上的commit()方法(注意:该方法会接收一个TransactionStatus对象),具体执行详情如下所示:
接下来一起看一下commit(TransactionStatus status)方法的源码吧,详情请参见下述源代码:
@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;if (defStatus.isLocalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Transactional code has requested rollback");}processRollback(defStatus, false);return;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus, true);return;}processCommit(defStatus);
}
这个方法会首先判断当前事务是否需要执行回滚操作。如果需要,则继续调用pocessRollback()方法。如果不需要,则直接调用processCommit(DefaultTransactionStatus status)方法。首先来看一下processCommit()方法的源码:
private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;boolean commitListenerInvoked = false;try {boolean unexpectedRollback = false;prepareForCommit(status);triggerBeforeCommit(status);triggerBeforeCompletion(status);beforeCompletionInvoked = true;if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;status.releaseHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction commit");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;doCommit(status);}else if (isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = status.isGlobalRollbackOnly();}// Throw UnexpectedRollbackException if we have a global rollback-only// marker but still didn't get a corresponding exception from commit.if (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");}}catch (UnexpectedRollbackException ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));throw ex;}catch (TransactionException ex) {if (isRollbackOnCommitFailure()) {doRollbackOnCommitException(status, ex);}else {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex));}}throw ex;}catch (RuntimeException | Error ex) {if (!beforeCompletionInvoked) {triggerBeforeCompletion(status);}doRollbackOnCommitException(status, ex);throw ex;}// Trigger afterCommit callbacks, with an exception thrown there// propagated to callers but the transaction still considered as committed.try {triggerAfterCommit(status);}finally {triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null));}}}finally {cleanupAfterCompletion(status);}
}
因为本次测试是一个正常流程,所以这里会直接走到doCommit(status)方法处,继续看这个方法的源码如下所示(注意这段源码位于DataSourceTransactionManager类中,关于其继承体系可以参考《Spring 事务原理总结三》这篇文章):
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 {con.commit();}catch (SQLException ex) {throw translateException("JDBC commit", ex);}
}
从这里不难看出,其主要目的就是通过程序与数据库之间的连接对象来提交事务(数据库层面的事务),conn.commit(),这里我有个疑问当单独利用JDBC进行手动事务控制时,会有一个将当前事务设置为false的操作,比如conn.setAutoCommit(false),为什么这里没看到呢?再次梳理一下事务提交的执行流程:
- TransactionAspectSupport#commitTransactionAfterReturning (@Nullable TransactionInfo txInfo)
- AbstractPlatformTransactionManager#commit(TransactionStatus status)
- AbstractPlatformTransactionManager#processCommit(DefaultTransactionStatus status)
- DataSourceTransactionManager#doCommit(DefaultTransactionStatus status)
下面让我们继续回到TransactionAspectSupport#invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中,执行完commitTransactionAfterReturning()方法后,该方法就结束了(后面直接return retVal),详细执行过程参见下图:
至此我们把事务正常执行的流程梳理完了,不过这个过程中还遗留了几个小问题,下一篇博客我会对这些问题进行详细跟踪,这些问题分别是:
- 为什么这里没看到conn.setAutoCommit(false)?
- Spring事务异常回滚的执行流程是什么?
- Spring事务失效的场景有那些?
- 本篇文章中我们梳理了完整的流程,但是还有一个地方梳理的不够完整,即调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象这个地方