spring事务源码剖析

1. transactional注解开启事务

  • value

  • transactionManager 指定事务管理器

  • propagation 事务传播行为

  • isolation 隔离级别 脏读 幻读 不可重复读 序列化

  • timeout 事务超时

  • 事务不能运行过长时间,因为事务可能涉及到对数据库锁定,会占用资源造成死锁,在特定时间内代码如果没有执行完毕,事务就会自动回滚,而不会等待其结束

  • readOnly 是否只读

  • 如果一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库数据,而不进行更新操作,此时我们给该事务执行只读属性,可以帮助数据库引擎优化事务,从而提升数据库读写效率

  • rollbackFor 指定异常情况下执行,默认情况下如果不指定的话,Exception 下除了RuntimeException及其子类的其他异常)不会回滚

  • rollbackForClassName rollbackFor,此处只是需要类名

  • noRollbackFor 不回滚的异常

  • noRollbackForClassName 不回滚的异常名

2.Transactional事务不生效的场景

  • 类内部访问:A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Transactional,在 a1 里面调用 a2;

  • 私有方法:将 @Transactional 注解标注在非 public 方法上;

  • 异常不匹配:@Transactional 未设置 rollbackFor 属性,方法返回 Exception 等异常;

  • 多线程:主线程和子线程的调用,线程抛出异常。

3. transactional概述

  • 本质是使用了jdbc的事务来进行事务控制

  • 基于spring的动态代理的机制实现

4.事务工作流程

第一块是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:

获取 Louzai 的切面方法:首先会拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行事务处理的方法,匹配成功的方法,还需要将事务属性保存到缓存 attributeCache 中。

创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。

第二块是事务执行,整个逻辑比较复杂,我只选取 4 块最核心的逻辑,分别为从缓存拿到事务属性、创建并开启事务、执行业务逻辑、提交或者回滚事务。

5.源码解析

5.1代码入口

5.2创建代理对象

  1. 先获取 louzai 类的所有切面列表;

  1. 创建一个 AOP 的代理对象。

5.2.1获取切面列表

这里有 2 个重要的方法,先执行 findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。

依次返回,重新来到 findEligibleAdvisors()。

进入 canApply(),开始匹配 louzai 的切面。

这里只会匹配到 Louzai.testSuccess() 方法,我们直接进入匹配逻辑。

如果匹配成功,还会把事务的属性配置信息放入 attributeCache 缓存。

我们依次返回到 getTransactionAttribute(),再看看放入缓存中的数据。

再回到该小节开头,我们拿到 louzai 的切面信息,去创建 AOP 代理对象。

5.2.2创建aop代理对象

5.3事务执行

回到业务逻辑,通过 louzai 的 AOP 代理对象,开始执行主方法。

因为代理对象是 Cglib 方式创建,所以通过 Cglib 来执行。

下面的代码是事务执行的核心逻辑 invokeWithinTransaction()。

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

        //获取我们的事务属源对象
        TransactionAttributeSource tas = getTransactionAttributeSource();
        //通过事务属性源对象获取到我们的事务属性信息
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        //获取我们配置的事务管理器对象
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        //从tx属性对象中获取出标注了@Transactionl的方法描述符
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        //处理声明式事务
        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            //有没有必要创建事务
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                //调用钩子函数进行回调目标方法
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                //抛出异常进行回滚处理
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                //清空我们的线程变量中transactionInfo的值
                cleanupTransactionInfo(txInfo);
            }
            //提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
        //编程式事务
        else {
          // 这里不是我们的重点,省略...
        }
    }

5.3.1获取事务属性

在 invokeWithinTransaction() 中,我们找到获取事务属性的入口。

从 attributeCache 获取事务的缓存数据,缓存数据是在 “2.2.1 获取切面列表” 中保存的。

5.3.2创建事务

通过 doGetTransaction() 获取事务。

protected Object doGetTransaction() {
        //创建一个数据源事务对象
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        //是否允许当前事务设置保持点
        txObject.setSavepointAllowed(isNestedTransactionAllowed());
        /**
         * TransactionSynchronizationManager 事务同步管理器对象(该类中都是局部线程变量)
         * 用来保存当前事务的信息,我们第一次从这里去线程变量中获取 事务连接持有器对象 通过数据源为key去获取
         * 由于第一次进来开始事务 我们的事务同步管理器中没有被存放.所以此时获取出来的conHolder为null
         */
        ConnectionHolder conHolder =
                (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
        txObject.setConnectionHolder(conHolder, false);
        //返回事务对象
        return txObject;
    }

通过 startTransaction() 开启事务。

下面是开启事务的详细逻辑,了解一下即可。

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对象中去
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            //标记当前的连接是一个同步事务
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();

            //为当前的事务设置隔离级别
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);

            //关闭自动提交
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }

            //判断事务为只读事务
            prepareTransactionalConnection(con, definition);
            //设置事务激活
            txObject.getConnectionHolder().setTransactionActive(true);

            //设置事务超时时间
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }

            // 绑定我们的数据源和连接到我们的同步管理器上   把数据源作为key,数据库连接作为value 设置到线程变量中
            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);
        }
    }

最后返回到 invokeWithinTransaction(),得到 txInfo 对象。

5.3.3执行逻辑分析

还是在 invokeWithinTransaction() 中,开始执行业务逻辑。

进入到真正的业务逻辑。

执行完毕后抛出异常,依次返回,走后续的回滚事务逻辑。

5.3.4 回滚事务

还是在 invokeWithinTransaction() 中,进入回滚事务的逻辑。

执行回滚逻辑很简单,我们只看如何判断是否回滚。

如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。

之所以用递归,不仅需要判断抛出异常的本身,还需要判断它继承的父类异常,满足任意一个即可捕获。

到这里,所有的流程结束。

猜你喜欢

转载自blog.csdn.net/qq_31671187/article/details/128632790