工作忙到死也要学会的Spring事务原理

工作忙到死也要学会的Spring事务原理

前言

之前想总结一下Spring的事务相关内容,但是上班事情特别多,所以只能每天写一点点,真的就是写一点点,但是功夫不负有心人,在耗时大半个月后,终于是把这篇写出来了。

本文的分析主要是针对基于@Transactional注解的声明式事务,相信这也是大部分的人会使用到的Spring的事务的功能。全文主要是从@Transactional注解如何生效,声明式事务的操作流程以及相关关键对象这些方面展开讨论,如果嫌正文太长,可以直接跳到总结进行阅读,相信也能有不小收获。

正文

一. @Transactional注解相关的AOP浅析

由于本文讨论的Spring的事务主要是基于@Transactional注解的声明式事务,而@Transactional注解实现声明式事务依赖SpringAOP机制,所以在本节先简单的对@Transactional注解相关的AOP机制进行一个分析。笔者强烈建议先阅读详细分析Spring的AOP源码上篇详细分析Spring的AOP源码下篇稍微深入一点学习SpringAOP原理,在理解了SpringAOP实现原理的基础上,再来学习@Transactional注解的声明式事务,简直不要太简单。

已知,在Springbean的生命周期中,一个bean会先被new出来,然后完成依赖注入,再然后会完成初始化,这里的初始化就是对当前bean应用各种BeanPostProcessor的逻辑,其中有一个BeanPostProcessorSpringAOP密切相关,这个BeanPostProcessor就是InfrastructureAdvisorAutoProxyCreator,其官方注释如下所示。

Auto-proxy creator that considers infrastructure Advisor beans only, ignoring any application-defined Advisors.

可以理解为这个BeanPostProcessor会负责Spring的一些基础功能的AOP的实现,而@Transactional注解的声明式事务功能也属于基础功能,所以也归InfrastructureAdvisorAutoProxyCreator管,InfrastructureAdvisorAutoProxyCreator的一个类图如下所示
请添加图片描述请添加图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

偌大的类图,我们的眼里只需要看到AbstractAutoProxyCreator即可,实际会在AbstractAutoProxyCreatorwrapIfNecessary() 方法中为存在@Transactional注解的bean生成动态代理对象,怎么理解存在@Transactional注解的bean呢,就是这个bean本身被@Transactional注解修饰,或者这个bean存在被@Transactional注解修饰的方法,我们这里简单看一下wrapIfNecessary() 方法的实现。

java复制代码protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

    ......

    // 拿到作用于当前bean的通知
    // 这里和@Transactional注解相关的通知为
    // BeanFactoryTransactionAttributeSourceAdvisor
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 生成动态代理对象
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    ......

}

那么我们只需要知道,由于SpringAOP机制,会为存在@Transactional注解的bean生成一个动态代理对象,并且生成的动态代理对象会持有一个名为BeanFactoryTransactionAttributeSourceAdvisor的通知。那么现在合理推断,Spring的事务的玄机应该就在BeanFactoryTransactionAttributeSourceAdvisor中,先看一下其类图,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么毫无疑问,BeanFactoryTransactionAttributeSourceAdvisor本质就是一个Advisor,再结合AOP的机制,这个Advisor最终会被封装为一个方法拦截器MethodInterceptor,这里就不卖关子了,BeanFactoryTransactionAttributeSourceAdvisor对应的拦截器叫做TransactionInterceptor(实现了MethodInterceptor接口)。到这里我们还不能遗忘BeanFactoryTransactionAttributeSourceAdvisor,因为BeanFactoryTransactionAttributeSourceAdvisor有一个很重要的字段叫做transactionAttributeSource,其类型为TransactionAttributeSource,所有@Transactional注解的属性都缓存在这个对象中,并且理所应当的,TransactionInterceptor也持有TransactionAttributeSourceBeanFactoryTransactionAttributeSourceAdvisor给的)。

好了,啰嗦了这么多,其实就一个意思,Spring会为所有有@Transactional注解的bean添加一个叫做TransactionInterceptor的方法调用拦截器,在调用我们的@Transactional注解修饰的方法前,首先会调用到TransactionInterceptor这个拦截器中,并且这个拦截器还很不讲武德的知道所有@Transactional注解的属性,所以,要学习Spring的事务,从哪里入手,现在应该知道了吧。

二. @Transactional注解事务机制源码分析

在本节分析中,从源码层面看一下Spring中基于@Transactional注解的事务实现机制,重点理解如下几个对象。

  1. TransactionAttribute。保存@Transactional注解的属性;
  2. TransactionManager。事务管理器,Spring中默认提供的事务管理器会和一个数据源绑定,且只能绑定一个;
  3. TransactionInfo。保存当前现成的事务的信息;
  4. TransactionStatus。持有一个事务对象并且记录着这个事务的状态。

废话不多说,开始分析。从第一节中我们知道,在调用我们的由@Transactional注解修饰的方法前,首先会调用到TransactionInterceptor这个拦截器,对应的入口方法是invokeWithinTransaction() 方法,老长的一个方法了,但是我们只需要关注里面的小部分代码,精简后的源码实现如下所示。

java复制代码@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
        final InvocationCallback invocation) throws Throwable {

    TransactionAttributeSource tas = getTransactionAttributeSource();
    // 1. 从TransactionAttributeSource中拿到当前方法的@Transactional注解的属性
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    // 2. 根据TransactionAttribute拿到事务管理器TransactionManager
    final TransactionManager tm = determineTransactionManager(txAttr);

    ......

    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 3. 创建事务并封装为TransactionStatus对象
        // 4. 将TransactionStatus对象封装为TransactionInfo并与当前线程绑定
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

        Object retVal;
        try {
            // 5. 调用实际的方法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 6. 调用实际的方法发生异常时需要提交或者回滚事务
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 7. 重置当前线程的TransactionInfo
            cleanupTransactionInfo(txInfo);
        }

        if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
            TransactionStatus status = txInfo.getTransactionStatus();
            if (status != null && txAttr != null) {
                retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
            }
        }

        // 8. 进行事务提交
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } else {

        ......

    }
}

上面的注释中的第1到第8点,基本就是使用@Transactional注解实现声明式事务时,Spring为我们做的事情,在这里千万别想太复杂,什么事务嵌套啊之类的先不要去考虑,先理解一下上面的整个流程,其实也很好理解吧,下面将对每个步骤的源码进行分析,逐步理解@Transactional注解实现声明式事务的原理。

1. 从TransactionAttributeSource中拿到当前方法的@Transactional注解的属性

这里不分析如何从一堆属性中找到当前方法的@Transactional注解的属性,而是来看一下@Transactional注解的属性对应的TransactionAttribute对象长啥样。首先看一下TransactionAttribute的类图,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们在@Transactional注解上配置的属性会被解析为TransactionAttribute的如下属性。

  1. qualifier。通过@Transactional注解的value或者transactionManager属性可以设置,用于指定要使用的事务管理器,多在多数据源的场景下使用,默认为空表示不指定事务管理器;
  2. propagationBehavior。通过@Transactional注解的propagation属性可以设置,用于指定事务的传播类型,默认为Propagation.REQUIRED
  3. isolationLevel。通过@Transactional注解的isolation属性可以设置,用于指定事务的隔离级别,默认使用数据库的默认隔离级别;
  4. timeout。通过@Transactional注解的timeout属性可以设置,单位是秒,用于指定事务的超时时间,默认使用底层事务系统的默认超时时间。这个事务超时时间,有点玄机,后面会进行一个详细说明。

好像没看到@Transactional注解很重要的一个属性rollbackFor呢,这是因为在TransactionAttribute中没有定义对应的相关属性,而是定义了一个rollbackOn() 方法,那么在TransactionAttribute接口的实现类中,合理的做法就是将@Transactional注解上通过rollbackFor等属性配置的异常保存下来,然后在实现的rollbackOn() 方法中判断是否需要回滚事务,例如TransactionAttribute接口的实现类RuleBasedTransactionAttribute就是这么做的。

2. 根据TransactionAttribute拿到事务管理器TransactionManager

TransactionManager叫做事务管理器,顾名思义,是用于管理事务的,TransactionManagerSpring中事务管理器的顶层接口,然而很遗憾,这是个空接口,如下所示。

java复制代码public interface TransactionManager {

}

我们这里主要讨论的是TransactionManager的子接口PlatformTransactionManager,其是Spring事务的一个核心接口,该接口定义了如下三个方法。

java复制代码public interface PlatformTransactionManager extends TransactionManager {

    // 根据指定的传播行为返回当前活动的事务或创建新事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    // 提交事务
    void commit(TransactionStatus status) throws TransactionException;

    // 回滚事务
    void rollback(TransactionStatus status) throws TransactionException;

}

看到上面三个方法,心里其实应该有点底了,事务管理器的基本功能就是开启事务,然后提交或者回滚事务,但是仅有上述的功能肯定是不行的,所以PlatformTransactionManager有一个抽象的实现类,叫做AbstractPlatformTransactionManager,该类的方法我们不去研究,但是可以研究一下官方对该类的注释,官方注释对该类的工作流程进行了如下总结。

  1. determines if there is an existing transaction。(获取事务时)确定当前是否有已经存在的事务;
  2. applies the appropriate propagation behavior。根据传播类型执行相应的逻辑;
  3. suspends and resumes transactions if necessary。必要时挂起和恢复事务;
  4. checks the rollback-only flag on commit。在提交时检查仅回滚标志;
  5. applies the appropriate modification on rollback(actual rollback or setting rollback-only)。对回滚进行适当的修改(回滚分为实际回滚和设置仅回滚);
  6. triggers registered synchronization callbacks(if transaction synchronization is active)。(如果事务同步是开启状态)触发注册的同步回调。

如果不看源码,仅从官方注释我们还是不太明白里面的一些概念,但是我们可以注意到有什么传播类型,挂起和恢复等字眼,大概猜到我们背了很久的事务传播类型的逻辑会由AbstractPlatformTransactionManager来实现,换言之AbstractPlatformTransactionManager替派生类屏蔽了事务传播类型的影响,派生类不需要去感知事务传播类型。实际上AbstractPlatformTransactionManager是典型的模板设计模式的应用,其提供了若干公共方法,会按照上述的工作流程对外提供服务,同时还定义了若干钩子方法来让派生类来实现各自的功能,部分钩子方法签名如下所示。

java复制代码// 开启新事务
// 无须关心事务传播类型
protected abstract void doBegin(Object transaction, TransactionDefinition definition)
        throws TransactionException;

// 挂起当前事务
// 派生类不重写则默认抛出TransactionSuspensionNotSupportedException
protected Object doSuspend(Object transaction) throws TransactionException

// 恢复事务
// 派生类不重写则默认抛出TransactionSuspensionNotSupportedException
protected void doResume(@Nullable Object transaction, Object suspendedResources) 
        throws TransactionException

// 实际提交事务
// 无须关心事务传播类型
protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException

// 实际回滚事务
// 无须关心事务传播类型
protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException

那么基本可以这么进行一个小结:AbstractPlatformTransactionManager提供了模板方法来规定事务操作的流程,在这个事务操作流程中也就将事务传播类型的逻辑考虑了进去,也就相当于替派生类屏蔽了事务传播类型的影响,然后AbstractPlatformTransactionManager的派生类仅需要实现AbstractPlatformTransactionManager定义的若干钩子方法,在这些钩子方法中完成事务实际的开启,挂起,恢复,提交和回滚等。

上面一直提到AbstractPlatformTransactionManager的派生类,那么到底有没有一个像模像样的标准的派生类用于我们学习呢,有的,这个派生类就是DataSourceTransactionManager,这是官方钦定的事务策略的实现指南,同时也是@Transactional注解实现声明式事务时默认会使用的事务管理器,我们这里暂时不深入DataSourceTransactionManager的源码,因为后面自会分析到,现在让我们先回到TransactionInterceptor这个拦截器的invokeWithinTransaction() 方法,在这个方法中有一步是调用到了determineTransactionManager() 方法来获取事务管理器,那么就从这里入手,看一下@Transactional注解实现声明式事务时是怎么拿到事务管理器的,源码实现如下所示。

java复制代码@Nullable
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
    if (txAttr == null || this.beanFactory == null) {
        return getTransactionManager();
    }

    // 拿到配置的事务管理器的qualifier
    String qualifier = txAttr.getQualifier();
    if (StringUtils.hasText(qualifier)) {
        // 先根据qualifier从容器中获取事务管理器
        return determineQualifiedTransactionManager(this.beanFactory, qualifier);
    } else if (StringUtils.hasText(this.transactionManagerBeanName)) {
        // 没有配置qualifier但存在事务管理器的beanName
        // 则根据beanName从容器中获取事务管理器
        return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
    } else {
        // 既没配置qualifier也不存在事务管理器的beanName
        // 则从容器中拿到默认的事务管理器
        TransactionManager defaultTransactionManager = getTransactionManager();
        if (defaultTransactionManager == null) {
            defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
            if (defaultTransactionManager == null) {
                defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
                this.transactionManagerCache.putIfAbsent(
                        DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
            }
        }
        return defaultTransactionManager;
    }
}

上述获取事务管理器的过程十分清晰,概括下来就是如果在@Transactional注解中通过value或者transactionManager属性配置了事务管理器,则使用配置的事务管理器,如果没有则使用Spring默认提供的事务管理器,这里的默认的事务管理器的类型是JdbcTransactionManager,类图如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其本质还是一个DataSourceTransactionManager,所以我们又回到DataSourceTransactionManager了,那么这里最后就小结一下DataSourceTransactionManager的特点:每个DataSourceTransactionManager都持有一个数据库连接池,并且只能持有一个,至于DataSourceTransactionManager其它的实现原理,后面会一一分析。

3. 创建事务并封装为TransactionStatus对象

TransactionInterceptor拦截器的invokeWithinTransaction() 方法中,获取到事务管理器后,会再调用createTransactionIfNecessary() 方法来创建事务或者拿到已经存在的事务,这里就以该方法作为入口,对如何从事务管理器中创建或拿到事务进行一个原理学习,首先看一下createTransactionIfNecessary() 方法的实现,源码如下所示。

java复制代码protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

    // 如果事务属性中没有指定事务的名字
    // 则使用对应的方法的全限定名作为事务的名字
    if (txAttr != null && txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName() {
                return joinpointIdentification;
            }
        };
    }

    // 从事务管理器中拿到事务并封装为TransactionStatus
    // TransactionStatus持有一个事务对象并且记录着这个事务的状态
    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");
            }
        }
    }
    // 将事务管理器,事务属性,事务方法的全限定名和TransactionStatus封装为TransactionInfo并返回
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

上述方法首先会检查事务属性TransactionAttribute中是否有设置事务的名字,这个名字作为事务的一个唯一标识,通常TransactionAttribute中一开始是没有的,所以上述方法会基于没有事务名字的事务属性创建一个委派事务属性对象出来,这个委派事务属性对象会重写原事务属性对象的getName() 方法并以事务作用的方法的全限定名作为事务的名字。再然后就是从事务管理器中获取TransactionStatus,这里的事务管理器默认情况下就是DataSourceTransactionManager,所以重点跟进DataSourceTransactionManager(实际是其父类AbstractPlatformTransactionManager)的getTransaction() 方法的实现,源码如下所示。

java复制代码public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

    // 这里的TransactionDefinition就是事务属性
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    // 拿到事务对象DataSourceTransactionObject
    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    // 判断当前线程是否已经存在一个事务
    if (isExistingTransaction(transaction)) {
        // 当前线程已经存在一个事务
        // 根据事务传播类型执行不同逻辑
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // 执行到这里表示当前没有已经存在的事务
    // 也就是需要基于事务属性def新开启一个事务

    // 判断新创建的事务的超时时间是否非法
    // TransactionDefinition.TIMEOUT_DEFAULT值为-1,表示使用底层事务系统的过期时间
    // 如果设置的超时时间小于-1,那么这个过期时间是非法的,会抛出异常
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }

    // 当前没有已经存在的事务,但是事务传播类型又是MANDATORY,此时需要抛出异常
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // 当前没有已经存在的事务,同时事务传播类型是PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW或者PROPAGATION_NESTED
        // 此时需要开启一个新事务,这个新事务由一个TransactionStatus对象持有,并最终返回这个TransactionStatus对象
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            // 开启新事务
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        } catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    } else {
        // 当前没有已经存在的事务,同时事务传播类型是SUPPORTS,NOT_SUPPORTED或者NEVER
        // 此时基于空事务对象创建一个TransactionStatus并返回
        if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
            logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                    "isolation level will effectively be ignored: " + def);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

上述方法包含了蛮多逻辑的,下面来依次顺着流程进行一个说明。

Ⅰ. 首先是调用了DataSourceTransactionManager实现的getTransaction() 钩子方法拿到一个事务对象,简单看一下该方法的实现。

java复制代码@Override
protected Object doGetTransaction() {
    // 创建一个事务对象出来
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    // 嵌套事务是基于Savepoint来实现
    // 所以是否支持Savepoint要和是否支持嵌套事务保持一致
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    // 拿到当前线程在当前事务管理器持有的数据源中的数据库连接
    ConnectionHolder conHolder =
            (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    // 一个事务对象持有一个数据库连接
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

这里的事务对象就是DataSourceTransactionObject,其会持有一个数据库连接的包装类ConnectionHolder,这个ConnectionHolder会从TransactionSynchronizationManager中获取,TransactionSynchronizationManager是事务的一个全局同步管理器,里面有各种大量的ThreadLocal来保存每个线程各自的事务的相关数据以做到线程间的事务隔离以及线程中的事务同步操作,因此如果当前线程之前已经开启过事务,那么这里就可以从TransactionSynchronizationManager中获取到一个ConnectionHolder,如果当前线程没有开启过事务,那么获取出来的ConnectionHoldernull,这很重要,因为后续会基于事务对象DataSourceTransactionObject中的ConnectionHolder是否为null来判断当前线程是否开启过事务,从而执行事务传播类型的相关逻辑。

Ⅱ. 拿到事务对象后,会判断当前线程是否已经开启过事务,对应的判断方法isExistingTransaction() 如下所示。

java复制代码@Override
protected boolean isExistingTransaction(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // 只有事务对象中存在ConnectionHolder且ConnectionHolder是开启状态才算作当前线程存在事务
    return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

认为当前线程存在事务的条件就是事务对象中存在ConnectionHolderConnectionHoldertransactionActive字段是trueConnectionHolder会在真正开启事务的时候将transactionActive字段置为true,这点后面会讲到。如果当前线程已经开启过事务,那么就要调用handleExistingTransaction() 方法来处理当前线程已经存在事务的情况,其实现如下。

java复制代码private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    // 如果事务传播类型是NEVER,则抛出异常,表示不支持当前线程已经存在事务的情况
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
    }

    // 如果事务传播类型是NOT_SUPPORTED,则挂起当前线程已经存在的事务,并以非事务状态执行
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction");
        }
        // 挂起当前线程已经存在的事务
        Object suspendedResources = suspend(transaction);
        // 标志位newSynchronization表示是否为对应事务开启了事务同步
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 基于空事务对象创建一个TransactionStatus并返回
        // 也就是以非事务的状态执行
        return prepareTransactionStatus(
                definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    }

    // 如果事务传播类型是REQUIRES_NEW,则挂起当前已经存在的事务,然后开启一个新事务
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction, creating new transaction with name [" +
                    definition.getName() + "]");
        }
        // 挂起当前事务
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            // 开启一个新事务
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        } catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }

    // 如果事务传播类型是NESTED,则在当前事务中创建Savenpoint以实现嵌套事务
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        if (!isNestedTransactionAllowed()) {
            throw new NestedTransactionNotSupportedException(
                    "Transaction manager does not allow nested transactions by default - " +
                    "specify 'nestedTransactionAllowed' property with value 'true'");
        }
        if (debugEnabled) {
            logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
        }
        if (useSavepointForNestedTransaction()) {
            // 创建嵌套事务
            DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
            // 创建保存点
            status.createAndHoldSavepoint();
            return status;
        } else {
            return startTransaction(definition, transaction, debugEnabled, null);
        }
    }

    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    if (isValidateExistingTransaction()) {
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                Constants isoConstants = DefaultTransactionDefinition.constants;
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
                        (currentIsolationLevel != null ?
                                isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                "(unknown)"));
            }
        }
        if (!definition.isReadOnly()) {
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is");
            }
        }
    }
    // 执行到这里假定事务传播类型是SUPPORTS或者REQUIRED
    // 则使用当前线程已经存在的事务,即加入这个事务
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

其实在handleExistingTransaction() 方法中就是在实现Spring定义的事务传播类型的语义,整体还是很清晰的,但是细究的话有几个点还是会让人疑惑,那就是上面在不同的代码分支里面有调用到prepareTransactionStatus() 方法和startTransaction() 方法,这两个方法会返回一个DefaultTransactionStatus对象,作为外层从事务管理器拿到的事务状态,这里的话暂不深究这两个方法以及TransactionStatus,稍后会对这两个方法以及TransactionStatus进行分析。

Ⅲ. 若当前没有已经存在的事务,则需要基于当前的事务属性新开启一个事务,此时要校验一下事务属性中的事务超时时间是否合法,非法的情况就是设置的事务超时时间小于-1,可以等于-1,-1表示使用底层事务系统默认的超时时间。

Ⅳ. 若当前没有已经存在的事务,则判断一下事务传播类型是不是MANDATORY,如果是,则根据MANDATORY的定义,此时需要抛出异常。

Ⅴ. 若当前没有已经存在的事务,则判断一下事务传播类型是不是REQUIREDREQUIRES_NEWNESTED,这三种事务传播类型在当前线程不存在事务的情况下,需要新开启一个事务。首先会调用AbstractPlatformTransactionManager#suspend方法并传入null得到一个SuspendedResourcesHolder,先说suspend() 方法,如果当前线程的事务同步是开启状态,则会先把当前线程的事务同步TransactionSynchronizationTransactionSynchronizationManager获取出来并调用每个TransactionSynchronizationsuspend() 方法来挂起事务同步,然后再在TransactionSynchronizationManager那边的各种ThreadLocal中把当前线程的事务信息重置,再然后把当前线程已经存在的事务使用的数据库连接获取出来并同时与当前线程解绑,最后把之前拿到的当前线程的事务同步,事务信息以及当前线程已经存在的事务的数据库连接封装为一个SuspendedResourcesHolder对象,所以suspend() 方法返回的SuspendedResourcesHolder对象实际就持有了被挂起事务使用的数据库连接以及被挂起事务的事务同步相关数据,下面看一下suspend() 方法的实现。

java复制代码@Nullable
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    // 当前线程开启了事务同步
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        // 从TransactionSynchronizationManager中将当前线程的事务同步TransactionSynchronization获取出来
        // 并且每一个事务同步TransactionSynchronization都会被调用suspend()方法以进行挂起
        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
        try {
            Object suspendedResources = null;
            if (transaction != null) {
                // 挂起传入的事务
                suspendedResources = doSuspend(transaction);
            }
            // 从事务管理器把当前线程的一些事务信息获取出来
            String name = TransactionSynchronizationManager.getCurrentTransactionName();
            TransactionSynchronizationManager.setCurrentTransactionName(null);
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
            Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
            boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
            TransactionSynchronizationManager.setActualTransactionActive(false);
            // 封装传入的事务使用的数据库连接,当前线程的事务同步和当前线程的事务信息为SuspendedResourcesHolder
            return new SuspendedResourcesHolder(
                    suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
        } catch (RuntimeException | Error ex) {
            doResumeSynchronization(suspendedSynchronizations);
            throw ex;
        }
    } else if (transaction != null) {
        // 传入的事务不为null
        // 则挂起传入的事务
        Object suspendedResources = doSuspend(transaction);
        return new SuspendedResourcesHolder(suspendedResources);
    } else {
        // 传入的事务为null且当前线程没有开启事务同步,则返回null
        return null;
    }
}

上述的suspend() 方法,最终会返回一个SuspendedResourcesHolder,这个SuspendedResourcesHolder就是一系列数据的组合,这些数据分为三部分:第一部分是传入的事务使用的数据库连接,第二部分是当前线程已有的事务的相关信息(事务名,是否只读标识,隔离级别和是否开启标识),第三部分是当前线程的所有事务同步TransactionSynchronization的集合。想必现在大家应该已经知道,所谓挂起事务,其实就是把被挂起事务的相关数据保存到SuspendedResourcesHolder中,并同时把被挂起事务的相关数据与当前线程解绑,最后被挂起事务对应的SuspendedResourcesHolder会被新开启的事务所持有,以便后续进行恢复。

再啰嗦一下,可能也有人会对上述的事务同步感到疑惑,特别是TransactionSynchronization这个东西到底是啥,其实这就是一个针对事务的操作的扩展,我们知道事务最终可以commit或者rollback,那么TransactionSynchronization可以在commit/rollback前后都做一些事情,就这么简单,大家可以自行看一下TransactionSynchronization接口的内容。

看完了suspend() 方法的实现,那么为什么要suspend(null) 肯定就很清楚了,首先肯定是没有事务给我们挂起的,其次就是如果开启了事务同步那么就把这些事务同步挂起,最后如果连事务同步也没开启,那么suspend(null) 就会返回一个null,表示挂了个寂寞。

无论挂的是不是寂寞,接下来就得开启新事务了,对应的方法是AbstractPlatformTransactionManager#startTransaction,看一下其实现。

java复制代码private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
        boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
    // 判断是否需要开启事务同步
    // 默认是需要开启的
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    // 创建事务状态DefaultTransactionStatus
    // 事务状态主要包含事务对象transaction和被挂起事务的数据suspendedResources
    DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    // 实际的开启事务并将开启的事务与当前线程绑定
    doBegin(transaction, definition);
    // 将事务的一些信息与当前线程绑定
    prepareSynchronization(status, definition);
    return status;
}

上述方法最终得到一个事务状态对象DefaultTransactionStatus,其最重要的组成是事务对象DataSourceTransactionObject和被挂起事务的数据SuspendedResourcesHolder,同时还有newTransaction字段用于标识是否是新创建的事务,newSynchronization字段用于标识是否新开启了事务同步,readOnly字段标识是否是只读事务。

上述方法真正开启事务的地方是调用钩子方法doBegin(),这里看一下DataSourceTransactionManagerdoBegin() 方法的实现,如下所示。

java复制代码@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        if (!txObject.hasConnectionHolder() ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 通常新开启事务时,事务对象中是没有数据库连接的
            // 这里需要从数据库连接池中获取一个数据库连接作为事务对象的数据库连接
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            // 封装数据库连接为一个ConnectionHolder然后设置给事务对象
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        // 将刚刚设置给事务对象的ConnectionHolder的synchronizedWithTransaction置为true
        // 表示这个ConnectionHolder已经由一个事务对象所持有了
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        // 这里将数据库连接获取出来
        con = txObject.getConnectionHolder().getConnection();

        // 如果事务属性中有对事务只读性或事务隔离级别做配置
        // 那么在这里将事务属性中的上述配置设置到数据库连接中
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        // 事务只读性也要设置给事务对象
        txObject.setReadOnly(definition.isReadOnly());

        // 如果是自动提交事务则关闭掉
        // 所以Spring中的事务不是自动提交的
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }

        prepareTransactionalConnection(con, definition);
        // 设置事务对象持有的ConnectionHolder的transactionActive为true
        // 表示当前ConnectionHolder对应一个开启的事务
        txObject.getConnectionHolder().setTransactionActive(true);

        // 设置事务超时时间
        // 实际就是在ConnectionHolder中设置一个超时时间点deadline
        // 超时时间点deadline的计算是当前时间加上超时时间
        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // 将ConnectionHolder与当前线程绑定
        // 也可以理解为将新开启的事务与当前线程绑定
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
        }
    } catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, obtainDataSource());
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}

上述方法比较重要的一点是将自动提交事务给关闭掉了,所以此时的事务是手动提交的。现在再看一下AbstractPlatformTransactionManager#startTransaction方法中调用到的prepareSynchronization() 方法的实现,该方法将事务的一些信息与当前线程绑定,如下所示。

java复制代码protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    // 新开启事务同步的情况下才需要将事务的信息与当前线程绑定
    if (status.isNewSynchronization()) {
        // 绑定事务开启标识
        TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
        // 绑定事务隔离级别
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
                definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
                        definition.getIsolationLevel() : null);
        // 绑定事务的只读标识
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
        // 绑定事务的名称
        TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
        // 激活当前线程的事务同步
        // 其实就是往ThreadLocal中添加一个空LinkedHashSet
        // 这个空LinkedHashSet后续用于存储TransactionSynchronization
        TransactionSynchronizationManager.initSynchronization();
    }
}

Ⅵ. 到这里的话,创建事务并封装为TransactionStatus对象基本就已经讲解完毕了,有点长,但是这部分内容其实算是Spring基于@Transactional注解实现声明式事务的核心,现在就进行一个小节。

首先是数据库连接,事务本质还是底层数据库进行支持,所以事务需要有数据库连接,这里的数据库连接通常从数据库连接池中拿到,例如从HikariCP数据库连接池中可以拿到一个HikariProxyConnection数据库连接,虽然是HikariCP对物理连接的又一层封装,但是我们姑且认为这就是数据库连接吧,所以此时我们有了一个数据库连接,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后是ConnectionHolder,其是Spring事务对从数据库连接的又一层封装,除了封装数据库连接以外,还提供了一些字段用于标识数据库连接的事务相关的属性,例如transactionActive布尔字段标识事务是否开启,savepointsSupported布尔字段标识是否支持保存点,deadline日期字段标识事务过期时间点,等等,所以此时我们有了一个ConnectionHolder,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

虽然事务本质是底层数据库进行支持,但是Spring要操作事务,肯定还是需要一个对象来表示事务,这个对象就是DataSourceTransactionObject,其对ConnectionHolder做了一层封装,然后也提供了一些字段用于标识代表的事务的属性,例如newConnectionHolder布尔字段标识是否是新创建的ConnectionHolderreadOnly布尔字段标识事务是否只读,等等,所以此时我们有了一个事务对象DataSourceTransactionObject,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

又已知Spring中有一个概念叫做事务的传播类型,并且不同的传播类型的表现行为大有不同,但是呢可以大致分为两种:使用当前线程已经存在的事务和挂起当前线程已经存在的事务,那么问题就来了,假如我们要挂起当前线程已经存在的事务,那么这个被挂起的事务我们就不管了吗,那自然是不能的,被挂起了也有被恢复的时候,此外,我们还得知道当前这个事务是使用的已经存在的事务,还是新创建的事务,所以在Spring中又提供了一个对象叫做DefaultTransactionStatus,其实现了TransactionStatus接口,表示Spring中的事务状态。DefaultTransactionStatus对两个重要对象进行了封装,其一是事务对象即DataSourceTransactionObject,其二是挂起资源对象即SuspendedResourcesHolder,也就是可以理解为DefaultTransactionStatus封装了当前使用的事务以及被挂起的事务,此外,DefaultTransactionStatus也提供了一些字段来标识当前事务的状态,例如newTransaction字段可以标识当前事务是新创建的还是之前已经存在的,completed字段可以标识事务是否已经完成,等等,所以此时我们有了一个事务状态对象DefaultTransactionStatus,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意,有一点很重要,就是在一个线程中,可能会调用多个由@Transactional注解修饰的方法,由于事务传播机制的存在,这些方法对应的事务使用的ConnectionHolder可以是同一个也可以不是同一个,但是事务对象和事务状态对象都是不同的,换言之,不同@Transactional注解修饰的方法的事务状态都是独立的,但是底层可以是同一个数据库连接的同一个事务,可以参考下图进行理解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么相信到这里,本小节的标题,“创建事务并封装为TransactionStatus对象”,具体怎么创建的事务,创建的事务是什么,为什么要封装为TransactionStatus对象,想必是比较清楚了。

4. 将TransactionStatus对象封装为TransactionInfo并与当前线程绑定

TransactionAspectSupport#createTransactionIfNecessary方法中,获取到事务状态对象DefaultTransactionStatus后,会再调用TransactionAspectSupport#prepareTransactionInfo方法,将事务管理器,事务属性,事务作用的方法的全限定名和事务状态再封装为一个TransactionInfo对象,并将得到的TransactionInfo对象与当前线程做绑定,下面看一下prepareTransactionInfo() 方法的实现,如下所示。

java复制代码protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, String joinpointIdentification,
        @Nullable TransactionStatus status) {

    // 基于事务管理器,事务属性和事务作用方法的全限定名创建TransactionInfo
    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        // 为TransactionInfo设置事务状态
        txInfo.newTransactionStatus(status);
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No need to create transaction for [" + joinpointIdentification +
                    "]: This method is not transactional.");
        }
    }

    // 将之前绑定到当前线程的TransactionInfo获取出来并保存到新创建的TransactionInfo中
    // 然后将新创建的TransactionInfo与当前线程绑定
    txInfo.bindToThread();
    return txInfo;
}

上述方法中,TransactionInfo的创建没什么好说的,主要在于调用了TransactionInfo#bindToThread方法来将当前线程旧的TransactionInfo保存到了新创建的TransactionInfo中,然后再将新创建的TransactionInfo与当前线程做绑定。

TransactionInfo对象包含最全的和事务相关的信息:事务管理器,事务属性,事务作用的方法全限定名,事务状态和旧的TransactionInfo,在TransactionAspectSupport#invokeWithinTransaction方法中拿到TransactionInfo后,后续的提交,回滚等操作都是基于TransactionInfo,并且TransactionInfo对应的事务结束后,还会将保存的旧的TransactionInfo重新与当前线程绑定。

5. 调用实际的方法
java
复制代码retVal = invocation.proceedWithInvocation();

准确的说,应该是在这里最终会调用实际的方法,因为首先需要拦截器链执行完毕后才会真正调用到被代理对象的方法,同时被代理对象可能还是一个代理对象,所以这里的方法可能会调用得很深很深,但在最后,实际的方法是会被调用到的。

6. 调用实际的方法发生异常时需要提交或者回滚事务

在调用到实际的方法的过程中,如果有发生异常,那么会调用到TransactionAspectSupport#completeTransactionAfterThrowing来进行“善后”,completeTransactionAfterThrowing() 实现如下所示。

java复制代码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);
        }
        // 如果当前发生的是需要回滚的异常
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 则回滚事务
                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 {
            // 如果发生的异常不是需要回滚的异常
            // 则根据配置来决定是需要回滚还是提交
            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;
            }
        }
    }
}

上述方法会先根据发生的异常在不在@Transactional注解配置的回滚异常中,如果在,就调用事务管理器的rollback() 方法进行事务回滚,如果不在,就调用事务管理器的commit() 方法进行事务提交或者回滚,下面分别分析一下事务管理器的rollback() 方法和commit() 方法。

首先是事务管理器的rollback() 方法,该方法在AbstractPlatformTransactionManager提供了统一实现,如下所示。

java复制代码@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() 方法中,如下所示。

java复制代码private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;

        try {
            // 这里会执行到事务同步TransactionSynchronization#beforeCompletion逻辑
            triggerBeforeCompletion(status);

            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                // 如果有保存点,则回滚事务到保存点的状态
                // 对应场景就是嵌套事务中,回滚子事务
                status.rollbackToHeldSavepoint();
            } else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                // 没有保存点且是新创建的事务,则回滚事务
                doRollback(status);
            } else {
                // 即没有保存点,也不是新创建的事务
                // 通常是在创建事务时当前线程有已经存在的事务,且事务隔离级别是SUPPORTS或者REQUIRED
                // 此时使用的是已经存在的事务,即加入了这个事务
                if (status.hasTransaction()) {
                    // 检查加入的事务的rollbackOnly标识,如果是true,表示需要将事务对应的ConnectionHolder设置为只回滚
                    // 或者事务管理器的globalRollbackOnParticipationFailure是true,此时也需要将事务对应的ConnectionHolder设置为只回滚
                    // 这种场景就是加入已经存在的事务但是又发生了异常,此时需要将事务标记为只回滚,防止事务后续被提交
                    // 默认情况下globalRollbackOnParticipationFailure为true,这是一种较为安全的策略
                    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");
                }
                if (!isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = false;
                }
            }
        } catch (RuntimeException | Error ex) {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw ex;
        }

        // 这里会执行到事务同步TransactionSynchronization#afterCompletion逻辑
        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

        if (unexpectedRollback) {
            throw new UnexpectedRollbackException(
                    "Transaction rolled back because it has been marked as rollback-only");
        }
    } finally {
        // 首先将事务状态对象的completed设置为true,表示当前方法的事务已经完成
        // 然后清空当前线程的事务同步信息,包括事务名,事务只读标识,事务隔离级别和事务开启标识
        // 再然后如果当前方法使用的是新创建的事务,那么需要将这个新创建的事务的信息进行清理
        // 最后如果当前事务有挂起另外一个事务,那么需要将挂起的事务恢复
        cleanupAfterCompletion(status);
    }
}

上述方法首先会在回滚的前后执行事务同步TransactionSynchronizationbeforeCompletion()afterCompletion() 方法,然后在回滚时会区分一下当前方法的事务传播类型,不同的事务传播类型,回滚策略会不同,再然后会清除当前方法的事务同步相关的信息,最后最重要的就是如果当前事务有挂起另外一个事务,那么会将这个被挂起的事务恢复,这里的恢复,其实就是把被挂起事务对应的ConnectionHolder重新和当前线程绑定,以及将事务同步相关信息重新和当前线程绑定。

分析完了事务管理器的rollback() 方法,现在继续分析事务管理器的commit() 方法,该方法在AbstractPlatformTransactionManager提供了统一实现,如下所示。

java复制代码@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");
    }

    // 如果设置了事务状态为只回滚
    // 则不提交并改为执行回滚操作
    // 默认情况下事务状态的只回滚标识是false
    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;
    }

    // 在提交事务前,会触发事务同步TransactionSynchronization的beforeCommit和beforeCompletion逻辑
    // 如果是嵌套事务场景,则释放保存点
    // 如果当前事务是一个新创建的事务,则提交这个事务
    // 事务提交后,触发事务同步TransactionSynchronization的afterCommit和afterCompletion逻辑
    // 最后如果有挂起的事务,则恢复这个被挂起的事务。
    processCommit(defStatus);
}

上面粘了一大堆代码,也给了一大堆注释,其实阐述的内容很简单,下面进行一个小结。

  1. 如果发生的异常是@Transactional注解配置的回滚异常,那么就进入回滚逻辑,如果不是,则进入提交逻辑;
  2. 回滚也可能不是真正的回滚,如果当前方法的事务是一个嵌套事务,那么仅回滚到保存点,如果当前方法的事务是加入的另外一个事务,则通常是把加入的这个事务的只回滚标识设置为true以期望后续这个事务不要提交,只有当前方法的事务是一个新创建的事务时,此时才进行回滚操作;
  3. 提交也不是一定会提交,如果当前方法的事务对应的事务状态的只回滚标识是true,则需要回滚这个事务,如果当前方法的事务被另外一个加入了该事务的方法把只回滚标识设置为了true,则还是需要回滚这个事务,最后如果都不满足前两种条件,则才会提交事务;
  4. 在回滚事务前,事务同步TransactionSynchronizationbeforeCompletion逻辑会执行,在回滚事务后,事务同步TransactionSynchronizationafterCompletion逻辑会执行;
  5. 在提交事务前,事务同步TransactionSynchronizationbeforeCommitbeforeCompletion逻辑会执行,在提交事务后,事务同步TransactionSynchronizationafterCommitafterCompletion逻辑会执行;
  6. 在回滚和提交事务的最后,如果有挂起的事务,则需要把挂起的事务恢复。
7. 重置当前线程的TransactionInfo

在调用了实际的方法之后,最后会在finally代码块中执行cleanupTransactionInfo() 方法来重新设置当前线程绑定的TransactionInfo,如何理解这个操作呢,首先我们回忆一下TransactionInfo是怎么创建的,对应方法是TransactionAspectSupport#prepareTransactionInfo,如下所示。

java复制代码protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, String joinpointIdentification,
        @Nullable TransactionStatus status) {

    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.newTransactionStatus(status);
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No need to create transaction for [" + joinpointIdentification +
                    "]: This method is not transactional.");
        }
    }

    // 将之前绑定到当前线程的TransactionInfo获取出来并保存到新创建的TransactionInfo中
    // 然后将新创建的TransactionInfo与当前线程绑定
    txInfo.bindToThread();
    return txInfo;
}

TransactionInfo的创建就两步:第一步是创建出TransactionInfo,第二步是将TransactionInfo与当前线程绑定,那么再看一下TransactionInfo#bindToThread的实现,如下所示。

java复制代码private void bindToThread() {
    // 从transactionInfoHolder中将当前线程绑定的TransactionInfo获取出来并保存到当前TransactionInfo中
    // 然后再将当前TransactionInfo与当前线程做绑定
    this.oldTransactionInfo = transactionInfoHolder.get();
    transactionInfoHolder.set(this);
}

实际上transactionInfoHolderTransactionInterceptor的一个ThreadLocal字段,用于保存和当前线程绑定的TransactionInfo,并且每一个TransactionInfo在与当前线程绑定前,都会先把之前绑定的TransactionInfo保存起来,等到当前的TransactionInfo结束并与当前线程解绑时,能够让被保存的TransactionInfo重新与当前线程绑定。

如此使用一个ThreadLocal来保存当前线程的TransactionInfo,其实就是为了在某一个事务方法中,可以方便的通过TransactionAspectSupport#currentTransactionStatus方法来拿到当前的事务状态对象TransactionStatus

8. 进行事务提交

好吧,逐渐接近尾声,痛苦又快乐着。

在执行实际方法的过程中,如果没有抛出异常,那么最后当然就是提交事务啦,这也是Spring的@Transactional注解的声明式事务的最大特色,自动开启和自动提交,发生异常时自动回滚。提交事务对应的方法是TransactionAspectSupport#commitTransactionAfterReturning,其实现如下所示。

java复制代码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());
    }
}

最终还是由事务管理器来完成事务提交,逻辑和第6小节中的是一样的,这里不再赘述。

总结

如果没记错,上面有几个坑应该还没填,不过篇幅实在是太长了,估计能全部看完的也没多少,所以上面没填的坑,留到后面的Spring事务的实战文章中继续填,下面对本文进行一个总结。

本文结合源码,对Spring基于@Transactional注解实现声明式事务的原理进行了一个分析,大体分为如下几个部分。

一. AOP相关

Spring基于@Transactional注解实现声明式事务,是通过切面完成的,对应的方法拦截器是TransactionInterceptor,也就是在调用被@Transactional注解修饰的方法前后,会执行TransactionInterceptor的相关逻辑,我们的事务开启,回滚或者提交,就是在TransactionInterceptor的逻辑中。

二. 一些关键类

了解如下关键类的作用,阅读Spring的事务的相关源码简直就是势如破竹。

1. TransactionAttribute

@Transactional注解上配置的属性会被解析为TransactionAttribute,后续的事务创建和使用,都会用到这个事务属性对象,像最常用的事务隔离级别和回滚异常,就分别对应TransactionAttributepropagationBehavior属性和rollbackOn() 方法。

2. TransactionManager

TransactionManager叫做事务管理器,是Spring中事务管理器的顶层抽象,由于这个接口一个方法都没有定义,显得过于抽象,所以Spring又定义了一个TransactionManager的子接口叫做PlatformTransactionManager,该接口定义了三个方法分别对应:获取事务,提交事务和回滚事务,所以我们可以知道,事务管理器的本质就对事务的获取,提交和回滚。

Spring提供了一个PlatformTransactionManager接口的抽象实现,叫做AbstractPlatformTransactionManager,这个抽象实现将Spring事务的传播类型相关的逻辑进行了统一实现,同时提供了一堆钩子方法以供派生类来做事务真正的提交,回滚,挂起和恢复等,也就是说由AbstractPlatformTransactionManager派生出来的事务管理器,不需要考虑事务的传播类型的逻辑的实现,只需要关心如何完成事务的提交,回滚,挂起和恢复。

我们本文主要讨论的事务管理器就是由AbstractPlatformTransactionManager派生得到的DataSourceTransactionManager,每个DataSourceTransactionManager都持有一个数据库连接池,并且只能持有一个,那么下面以一张图描绘一下上述事务管理器的关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. ConnectionHolder

ConnectionHolderSpring对数据库连接的封装,因为事务本质是底层数据库进行支持,所以Spring定义了ConnectionHolder对数据库连接做了一层包装,这样方便Spring管理事务底层的数据库连接以及定义相关字段进行功能扩展。

4. DataSourceTransactionObject

DataSourceTransactionObjectSpring定义的表示事务的对象,对ConnectionHolder做了一层封装并定义了相关字段进行功能扩展。

有一点很重,就是Spring中基于@Transactional注解实现的声明式事务,是以方法为粒度进行开启,并且Spring的事务还有事务传播类型的概念,那么由@Transactional注解修饰的不同的方法,无论事务传播类型是什么,这些方法的事务对象都是不同的,但是里面的ConnectionHolder可能是同一个也可能是不同的,例如当前方法是REQUIRED事务传播类型,并且当前线程已经有一个事务,那么就会使用这个已经存在的事务,那么就会复用这个事务对应的数据库连接,也就是复用这个ConnectionHolder,再例如当前方法是REQUIRES_NEW事务传播类型,并且当前线程也已经有一个事务,那么这个已经存在的事务会被挂起,然后创建一个新的事务,相应的,就会使用一个新的数据库连接,也就是一个新的ConnectionHolder

5. SuspendedResourcesHolder

Spring的事务有传播类型这个概念,那么时有会听到说挂起某个事务,那么这个被挂起的事务,怎么挂的,挂到哪里了呢。其实挂起一个事务,就是把这个事务对应的数据库连接先保存起来,暂时不用,然后保存在SuspendedResourcesHolder中。

6. TransactionStatus

TransactionStatus是事务状态接口,Spring提供了一个默认实现叫做DefaultTransactionStatus,其对事务对象DataSourceTransactionObjectSuspendedResourcesHolder进行了封装,所以简单的来说,DefaultTransactionStatus代表着当前使用的事务以及被挂起的事务。

7. TransactionInfo

TransactionInfo对象包含最全的和事务相关的信息:事务管理器,事务属性,事务作用的方法全限定名,事务状态和旧的TransactionInfo。之前谈到,ConnectionHolder包了一个真实数据库连接,事务对象DataSourceTransactionObject包了一个ConnectionHolderTransactionStatus包了一个DataSourceTransactionObject,那么这里TransactionInfo包了一个TransactionStatus,并且包到这里就到头了,后续使用的就都是TransactionInfo

三. 事务同步

可能时有听到说事务同步和TransactionSynchronizationManager,那么这个到底是个什么东西呢,别急,先看一下TransactionSynchronizationManager的一些字段。

java复制代码public abstract class TransactionSynchronizationManager {

    ......

    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");

    ......

}

那么这里总结一下Spring中的事务同步,其实有两部分。

  1. 事务同步管理器TransactionSynchronizationManager会保存当前事务的信息,例如事务名,事务只读标识和事务隔离级别等;
  2. 事务同步管理器TransactionSynchronizationManager会保存当前事务对应的TransactionSynchronization

上述第一点好理解,那第二点中提到的TransactionSynchronization是个什么东西呢,其就是Spring提供的对事务在提交和回滚前后执行一些操作的扩展点,例如TransactionSynchronization#beforeCommit会在事务提交前被调用,TransactionSynchronization#afterCommit会在事务提交后被调用。

四. 事务传播类型

我们在为每一个方法创建事务对象时,都会去获取当前线程使用的事务对应的ConnectionHolder,那么如果之前有开启事务,那么获取到的ConnectionHolder不为null,如果之前没有开启过事务,那么获取到的ConnectionHolder就为null,那么基于这一点,事务的传播类型的判断逻辑其实就很简单了,下图进行一个概括。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


如果觉得本文对你有帮助,麻烦点个赞,添加个收藏并点个关注吧,谢谢。

作者:半夏之沫
链接:https://juejin.cn/post/7271871038367662080
teger> currentTransactionIsolationLevel =
new NamedThreadLocal<>(“Current transaction isolation level”);

private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");

......

}


那么这里总结一下**Spring**中的事务同步,其实有两部分。

1. 事务同步管理器**TransactionSynchronizationManager**会保存当前事务的信息,例如事务名,事务只读标识和事务隔离级别等;
2. 事务同步管理器**TransactionSynchronizationManager**会保存当前事务对应的**TransactionSynchronization**。

上述第一点好理解,那第二点中提到的**TransactionSynchronization**是个什么东西呢,其就是**Spring**提供的对事务在提交和回滚前后执行一些操作的扩展点,例如**TransactionSynchronization#beforeCommit**会在事务提交前被调用,**TransactionSynchronization#afterCommit**会在事务提交后被调用。

### 四. 事务传播类型

我们在为每一个方法创建事务对象时,都会去获取当前线程使用的事务对应的**ConnectionHolder**,那么如果之前有开启事务,那么获取到的**ConnectionHolder**不为**null**,如果之前没有开启过事务,那么获取到的**ConnectionHolder**就为**null**,那么基于这一点,事务的传播类型的判断逻辑其实就很简单了,下图进行一个概括。

[外链图片转存中...(img-95CGGpIN-1694520179671)]

------

如果觉得本文对你有帮助,麻烦点个赞,添加个收藏并点个关注吧,谢谢。



> 作者:半夏之沫
> 链接:https://juejin.cn/post/7271871038367662080
> 来源:稀土掘金
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值