今天是二零二四年二月十八,农历正月初九。同时今天也是农历新年假期后的第一个工作日。我的内心既兴奋,又担忧,更急躁。兴奋是因为假期后的第一个工作日工作轻松;担忧是因为经过了这么长时间,我依旧没搞明白Spring事务,总觉得有些东西没搞懂,却又不知道是哪里,所以只能在这里徘徊不前;急躁是因为许多事情都要用钱去解决,自己那微薄的收入根本就是杯水车薪,而自己又不知道用哪种方式去筹集足够的资金来解决问题,所以只能整日急不可耐,而问题依旧。到底该怎么办呢?∙∙∙∙∙∙
针对第二种心理,我觉得没有其他办法,只能继续梳理,但也不能因此而放弃后面知识点的梳理。今天就让我们来聊一下Spring事务的几个面试题吧!我想围绕下面这样几个问题展开:
首先回忆一下前面几篇文章的主要内容:《Spring事务原理总结一》主要梳理了事务的基本概念及特征,同时也梳理了Spring事务的基本用法;《Spring事务原理总结二》则主要梳理了Spring框架解析及注册事务代理的流程;《Spring事务原理总结三》则主要梳理了Spring事务的一些核心组件及其继承结构;《Spring事务原理总结四》梳理了Spring事务的执行流程;《Spring事务原理总结五》主要梳理并回答了前一篇文章中遗留的几个问题;《Spring事务原理总结六》主要梳理了Spring事务的异常回滚流程。接着让我们试着利用这些文章梳理的内容来回答这两个问题。
首先看第一个问题,将TransactionInfo对象绑定到当前线程的操作,在《Spring事务原理总结四》这篇文章中有提到,如下图所示:

当时只是说了这些代码位于TransactionAspectSupport类中,不过并没有深究其意义。现在让我们细化一下这个说法,然后在梳理完第二个问题后来回答这个问题。TransactionInfos是TransactionAspectSupport类中的最终静态内部类(该类内部定义了bindToThread()方法和restoreThreadLocalStatus()方法。所以截图中说前者位于TransactionAspectSupport类的说法也是对的,因为其所在的类位于TransactionAspectSupport类中),其源码如下所示:
- protected static final class TransactionInfo {
-
- @Nullable
- private final PlatformTransactionManager transactionManager;
-
- @Nullable
- private final TransactionAttribute transactionAttribute;
-
- private final String joinpointIdentification;
-
- @Nullable
- private TransactionStatus transactionStatus;
-
- @Nullable
- private TransactionInfo oldTransactionInfo;
-
- public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,
- @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
-
- this.transactionManager = transactionManager;
- this.transactionAttribute = transactionAttribute;
- this.joinpointIdentification = joinpointIdentification;
- }
-
- public PlatformTransactionManager getTransactionManager() {
- Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
- return this.transactionManager;
- }
-
- @Nullable
- public TransactionAttribute getTransactionAttribute() {
- return this.transactionAttribute;
- }
-
- /**
- * Return a String representation of this joinpoint (usually a Method call)
- * for use in logging.
- */
- public String getJoinpointIdentification() {
- return this.joinpointIdentification;
- }
-
- public void newTransactionStatus(@Nullable TransactionStatus status) {
- this.transactionStatus = status;
- }
-
- @Nullable
- public TransactionStatus getTransactionStatus() {
- return this.transactionStatus;
- }
-
- /**
- * Return whether a transaction was created by this aspect,
- * or whether we just have a placeholder to keep ThreadLocal stack integrity.
- */
- public boolean hasTransaction() {
- return (this.transactionStatus != null);
- }
-
- private void bindToThread() {
- // Expose current TransactionStatus, preserving any existing TransactionStatus
- // for restoration after this transaction is complete.
- this.oldTransactionInfo = transactionInfoHolder.get();
- transactionInfoHolder.set(this);
- }
-
- private void restoreThreadLocalStatus() {
- // Use stack to restore old transaction TransactionInfo.
- // Will be null if none was set.
- transactionInfoHolder.set(this.oldTransactionInfo);
- }
-
- @Override
- public String toString() {
- return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");
- }
- }
这个类上有bindToThread()方法和restoreThreadLocalStatus()两个方法,它们的主要作用就是更改当前线程持有的ThreadLocal上的TransactionInfo值。
下面让我们来看一下第二个问题,要回答这个问题,需要从TransactionInterceptor的invoke(MethodInvocation)方法看起,这个方法的代码如下所示:
- @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);
-
- // ……
- // 这里删除了一些无用代码
-
- 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 exception
- completeTransactionAfterThrowing(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;
- }
- }
当调用事务代理对象时,程序会经过判断后,走进if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支。首先让我们考虑一下非事务嵌套的操作场景,if分支中的retVal = invocation.proceedWithInvocation();就表示业务代码执行完成了,后面就是释放资源(即调用cleanupTransactionInfo(txInfo)方法)、提交事务(则是指调用commitTransactionAfterReturning(txInfo)方法)等一系列常规操作。这样整个事务和业务处理逻辑就执行完成了。接着再来考虑一下事务嵌套的处理场景(即需要事务的业务处理代码中调用了另外一个需要事务的业务代码),这个时候第一次进入if分支的程序执行完retVal = invocation.proceedWithInvocation();后,会再次进入到if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支,这个时候我们需要关注if分支中的TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification)这句,先来看一下这个被调用的方法(createTransactionIfNecessary())及其关联方法(prepareTransactionInfo())的源码:
- 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) {
- @Override
- public 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);
- }
- //
- 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;
- }
这时我们主要关注prepareTransactionInfo()方法中的txInfo.bindToThread(),关于这个被调用的方法的详情可以看参看前面的源码。这里再贴一下图片:

从图中不难看出,这个方法会先从当前线程的ThreadLocal中拿出一个TransactionInfo对象,并将其赋值给TransactionInfo的oldTransactionInfo属性,然后将新的TransactionInfo对象重新赋值到当前线程的ThreadLocal中。接着就是就是继续调用retVal = invocation.proceedWithInvocation(),然后调用cleanupTransactionInfo(txInfo)方法,最后再调用commitTransactionAfterReturning(txInfo)方法。这里我们再啰嗦一下cleanupTransactionInfo ()方法,其源码如下图所示:
- protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
- if (txInfo != null) {
- txInfo.restoreThreadLocalStatus();
- }
- }
该方法最终调用的是TransactionInfo类中的restoreThreadLocalStatus()方法,其源码如下图所示:

说白了就是将当前TransactionInfo对象中的oldTransactionInfo对象重新赋值到当前线程的ThreadLocal对象中,这个对象就是嵌套事务的上一层事务。至此我们就可以用自己的语言来回答前面的两个问题了!
总的来看,将TransactionInfo绑定到当前线程的主要目的就是解决嵌套事务的。Spring解决嵌套的主要思路就是先将当前的TransactionInfo对象绑定到当前线程,当当前TransactionInfo对应的业务处理代码调用其他事务代理时,会将当前线程中保存的TransactionInfo对象赋值给新的TransactionInfo对象的oldTransactionInfo属性,然后将新的TransactionInfo对象重新绑定到当前线程的ThreadLocal上,这样就实现了前后两个事务的关联,并完成对事务传播行为的处理。下面这幅图展示了出现事务嵌套情况时的处理逻辑:
