• SqlSessionTemplate源码解析


    简介

    SqlSessionTemplate是mybatis-spring中最核心的一个类,我们知道MyBatis暴露出的最外层接口是SqlSession,所有的操作都是借助SqlSession接口的方法来完成的。MyBatis本身有一个默认实现类,也是我们在单独使用MyBatis时最常见的一个实现类DefalutSqlSession。而当我们将MyBatis与Spring整合时,便不再使用这个默认实现了,取而代之的是SqlSessionTemplate。与默认实现相比主要有如下区别:

    • SqlSessionTemplate是线程安全的,可以被多个DAO共享,而DefaultSqlSession线程不安全。至于线程不安全的原因显而易见,因为一个DefaultSqlSession实际可以代表一个Connection,如果在多线程中使用时,一个线程在执行数据库操作,另一个线程执行别的操作时直接将事务提交了,岂不是就乱套了。因此MyBatis官方文档建议DefaultSqlSession的最佳作用域是方法作用域。
    • SqlSessionTemplate是不支持事务以及关闭方法的,也就是commitrollback以及close。如果显示调用这几个方法,会抛出一个异常。事务的提交回滚以及SqlSession的关闭全都由自己自动管理,不需要外部程序参与。

    前置知识

    想要看懂这篇文章,首先需要熟悉MyBatis本身的工作流程。其次,因为这个类涉及了很多与Spring事务相关的知识点,因此还需要熟悉Spring的事务机制与原理。

    对于Spring事务的事务机制,可以看以下两篇文章。

    SpringManagedTransaction

    在分析SqlSessionFactoryBean时,对这个新的事务对象以及这个对象的工厂类只是一笔带过,在分析SqlSessionTemplate之前,有必要先说明下这个类。

    MyBatis本身内部有提供事务相关的API,但是与Spring整合后,需要将事务交给Spring来管理,以前的JdbcTransaction是不能与Spring一起工作的。而SpringManagedTransaction就是为了与Spring整合而设计的一个新的事务(Transaction接口)实现类。

    先看SpringManagedTransaction的创建,一般都是使用SpringManagedTransactionFactory这个工厂类来创建。

    public class SpringManagedTransactionFactory implements TransactionFactory {
    
        /**
         * 会忽略隔离级别以及自动提交这两个参数
         */
        @Override
        public Transaction newTransaction(DataSource dataSource, 
                                          TransactionIsolationLevel level, 
                                          boolean autoCommit) {
            return new SpringManagedTransaction(dataSource);
        }
    	// 其余方法略
    }
    
    
    public class SpringManagedTransaction implements Transaction {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(
            SpringManagedTransaction.class);
    
        private final DataSource dataSource;
    
        private Connection connection;
    
        private boolean isConnectionTransactional;
    
        private boolean autoCommit;
    
        /** 只是简单的赋值 **/
        public SpringManagedTransaction(DataSource dataSource) {
            notNull(dataSource, "No DataSource specified");
            this.dataSource = dataSource;
        }
    
      	/**
      	 * 获取一个连接,通过DataSourceUtils.getConnection实现
      	 * 如果有事务(Spring管理),则会返回当前线程绑定的连接
      	 * 否则从数据源中拿到一个新连接
      	 */
        @Override
        public Connection getConnection() throws SQLException {
            if (this.connection == null) {
                openConnection();
            }
            return this.connection;
        }
        // 获取连接, 全都是DataSourceUtils里的方法,因此需要先弄懂这个类
        private void openConnection() throws SQLException {
            this.connection = DataSourceUtils.getConnection(this.dataSource);
            this.autoCommit = this.connection.getAutoCommit();
            this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(
                this.connection, this.dataSource);
        }
    
       /**
        * 提交操作,只有不是Spring事务管理的连接,并且这个连接从数据源中取出来就需要手动提交
        * 时才提交
        */
        @Override
        public void commit() throws SQLException {
            if (this.connection != null && 
                !this.isConnectionTransactional && !this.autoCommit) {
                LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
                this.connection.commit();
            }
        }
        /**
         * 同提交
         */
        @Override
        public void rollback() throws SQLException {
            if (this.connection != null && 
                !this.isConnectionTransactional && !this.autoCommit) {
                LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
                this.connection.rollback();
            }
        }
    	/**
    	 * 将连接释放
    	 */
        @Override
        public void close() {
            DataSourceUtils.releaseConnection(this.connection, this.dataSource);
        }
    }
    
    
    

    可以看到这个类非常简单,前提时必须要把提到的前置知识搞明白。

    源码分析

    接下来就要从源码层面上揭开上述所说两点的秘密了。

    先看构造方法,类中提供了三个重载构造方法,我们看参数最多的那个即可。

    /**
     * sqlSessionFactory: 用于创建SqlSesion的工厂类,可以使用SqlSessionFactoryBean来构建,
     *                    SqlSessionFactoryBean前文已介绍过。
     * executorType: 指定SqlSession中Executor的类型,默认是SimpleExecutor,有以下几个选项
     *               ExecutorType.SIMPLE、ExecutorType.REUSE、 ExecutorType.BATCH
     * exceptionTranslator: 异常转换器,将MyBatis中的异常转换成Spring中的DataAccessException异常
     */
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                              PersistenceExceptionTranslator exceptionTranslator) {
    	//对必要参数的非空判断
        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        notNull(executorType, "Property 'executorType' is required");
    
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // 使用JDK动态代理创建一个SqlSession代理,以后所有的数据库操作就委托给它啦
        // 因此这一步是关键
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
    }
    

    代理拦截

    private class SqlSessionInterceptor implements InvocationHandler {
        // 拦截方法,每当调用sqlSession中的方法时,都会先进入到这里。
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 为什么说SqlSessionTemplate是线程安全的,就是下面这段代码了
            // 每次调用方法,都会去获取一个SqlSession(其实就是DefaultSqlSession)
            // 这个方法非常重要,下面会详细说。现在我们只要知道,使用这个方法不会每次都新建一个SqlSession
            // 主要是以下两种情况:
            // 1. 在一个事务期间(特指是在Spring事务管理)内,拿的这个SqlSession是同一个,会使用		           // ThreadLocal将SqlSession绑定到当前线程,因此在一个事务方法内多次调用insert、update、   
            // select等方法使用的是同一个SqlSession,而且线程之间是隔离的。对于这种情况,事务的回滚和
            // 提交全部由Spring事务管理设施自动操作。
            // 2. 在Spring管理的事务之外,每次拿到的SqlSession都是一个新的。
            //    2.1 从数据源拿到的连接(conn.getAutoCommit() == false)是手动提交的,那么每执行一次
            //        都会自动提交或者回滚。即一个操作就是一个事务。
            //    2.2 从数据源拿到的连接是自动提交的,这种情况就不用说了。
            SqlSession sqlSession = getSqlSession(
                SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType,
                SqlSessionTemplate.this.exceptionTranslator);
            try {
                // 执行拦截方法
                Object result = method.invoke(sqlSession, args);
                // 非事务,即不是运行在Spring事务方法getTransaction,rollback/commit之间
                if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.
                                               sqlSessionFactory)) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    // 对应第2种情况,commit会判断conn.getAutoCommit()的值是否需要调用    		
                    // conn.commmit();
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                // 转换异常,略
                Throwable unwrapped = unwrapThrowable(t);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                if (sqlSession != null) {
                    // 关闭sqlSession
                    // 如果是在Spring管理的事务中,只是将此次事务中获取SqlSession的次数减一
                    // 真正的关闭动作是在钩子(beforeCompletion或afterCompletion)回调时调用
                    // 如果不是在Spring管理的事务,这直接掉用sqlSessiion.close方法
                    // 其实我们看到上面出现异常其实没有调用sqlSession.rollback方法,这会在close
                    // 时智能的判断需不需要回滚。
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
        }
    }
    

    下面来分析getSqlSessionisSqlSessionTransactional以及closeSqlSession这三个关键方法,它们都是SqlSessionUtils里的工具方法,负责管理SqlSession的生命周期。

    先看getSqlSession

    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    	// 检查参数
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
        notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    	// 从当前线程获取绑定的SqlSessionHolder
        // 如果不为null,说明是在Spring事务管理的环境下运行,直接返回里面的sqlSession
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
            .getResource(sessionFactory);
    
        // 获取SqlSession,看下面方法解释
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        }
    
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }
    	// 走到这里说明SqlSession为null
        // 1. Spring事务管理之外的情况,每次都会获取一个新的SqlSession(DefaultSqlSession)
        // 2. Spring事务管理内,那么是事务内首次获取SqlSession,接下来会将这个SqlSession
        //    绑定到当前线程。
        session = sessionFactory.openSession(executorType);
    
        // 如果是上述说的第2中情况,则准备绑定SqlSession到当前线程,并且注册SqlSessionSynchronization
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
        return session;
    }
    
    private static SqlSession sessionHolder(ExecutorType executorType, 
                                            SqlSessionHolder holder) {
        // 如果不是Spring管理的事务中,那么holder = null
        // 直接返回null
        SqlSession session = null;
        if (holder != null && holder.isSynchronizedWithTransaction()) {
            // 检查下同一Spring管理的事务中再次获取SqlSession时的executorType
            // 如果与第一次不一样,抛出异常,第一次获取时会绑定到当前线程
            // 供在同一个事务中再次获取时使用。
            if (holder.getExecutorType() != executorType) {
                throw new TransientDataAccessResourceException("Cannot change the  	ExecutorType when there is an existing transaction");
            }
    		// 获取次数加1
            holder.requested();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
            }
            session = holder.getSqlSession();
        }
        return session;
    }
    
    private static void registerSessionHolder(SqlSessionFactory sessionFactory, 
                                              ExecutorType executorType,
                                              PersistenceExceptionTranslator                 
                                              exceptionTranslator, 
                                              SqlSession session) {
        SqlSessionHolder holder;
        // 只有是在开启了Spring事务时,这个方法才返回true
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            Environment environment = sessionFactory.getConfiguration().getEnvironment();
    
            // 如果MyBatis中使用的事务工厂是SpringManagedTransactionFactory(默认)
            if (environment.getTransactionFactory() instanceof SpringManagedTransaction
                Factory) {
    			// 创建一个SqlSessionHolder
                holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
                // 绑定到当前线程,供后续同一Spring事务中使用
                TransactionSynchronizationManager.bindResource(sessionFactory, holder);
                // 注册一个事务回调的接口,这里的方法依次会在Spring事务的某个阶段触发
                // 上面绑定的SqlSessionHolder就是在这里的钩子方法注销的,往下看吧
                TransactionSynchronizationManager.registerSynchronization(
                    new SqlSessionSynchronization(holder, sessionFactory));
                // 设置值为true,sessionHolder方法内有用到
                holder.setSynchronizedWithTransaction(true);
                // 获取次数加1
                holder.requested();
            } else {
                // Spring事务使用的数据源与MyBatis中的数据源不一致,那么还是运行在Spring事务之外咯
                // 其实我觉得这一行应该要先判断,然后再判断事务工厂,再绑定sqlSession?
                // 这里可以讨论下?
                if (TransactionSynchronizationManager.getResource(
                    environment.getDataSource()) == null) {
                    LOGGER.debug(() -> "SqlSession [" + session
                                 + "] was not registered for synchronization because DataSource is not transactional");
                } else {
                    // Spring事务使用的数据源与MyBatis中的数据源一致,抛出异常.
                    throw new TransientDataAccessResourceException(
                        "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
                }
            }
        } else {
            // 不是在Spring事务中运行
            LOGGER.debug(() -> "SqlSession [" + session
                         + "] was not registered for synchronization because synchronization is not active");
        }
    
    }
    
    // 最后再看下这个事务的回调吧,这个类最终肯定要实现TransactionSynchronization接口的
    // 其实可以发现这个类的逻辑和Spring中DataSourceUtils中的ConnectionSynchronization逻辑几乎完全一致
    // 主要关注这几个钩子方法吧
    private static final class SqlSessionSynchronization extends TransactionSynchronization
    -Adapter {
        
        // 事务被挂起时,解绑绑定的sqlSessionHolder
        public void suspend() {
            if (this.holderActive) {
                LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]");
                TransactionSynchronizationManager.unbindResource(this.sessionFactory);
            }
        }
        // 与suspend对应
        public void resume() {
          if (this.holderActive) {
            LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
          }
        }
    
       
       // 这里被触发是因为调用了transactionManager.commit()方法,Spring事务马上可能就要提交了
       // 这里需要调用下sqlSession.commit()以完成MyBatis内部缓存的相关工作以及刷新批处理
       // 如果不调用的话,那么在sqlSession.close()时,内部就会走回滚逻辑了。
       // 当然了这里虽然调用sqlSession.commit(),但是对于数据库事务因为会调用MyBatis事务对象的commit
       // 而SpringManagedTransaction这个对象的commit有做过判断,因此不会影响Spring的事务提交
        @Override
        public void beforeCommit(boolean readOnly) {
          if (TransactionSynchronizationManager.isActualTransactionActive()) {
            try {
              LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
              this.holder.getSqlSession().commit();
            } catch (PersistenceException p) {
              if (this.holder.getPersistenceExceptionTranslator() != null) {
                DataAccessException translated = this.holder.getPersistenceExceptionTranslator()
                    .translateExceptionIfPossible(p);
                if (translated != null) {
                  throw translated;
                }
              }
              throw p;
            }
          }
        }
    
       // 整个事务即将完成,需要清理线程中绑定的SqlSession,以及关闭
       // 这里曾经有一个bug,可以去看下Issue #18的有关讨论
       // 主要是JTA事务,其实对于DataSourceTransactionManager这个事务管理器是不存在这个Issue所说的问题
        @Override
        public void beforeCompletion() {
          // Issue #18 Close SqlSession and deregister it now
          // because afterCompletion may be called from a different thread
          if (!this.holder.isOpen()) {
            LOGGER
                .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.unbindResource(sessionFactory);
            this.holderActive = false;
            LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
            this.holder.getSqlSession().close();
          }
        }
    	// 同上
        @Override
        public void afterCompletion(int status) {
          if (this.holderActive) {
            // afterCompletion may have been called from a different thread
            // so avoid failing if there is nothing in this one
            LOGGER
                .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
            this.holderActive = false;
            LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
            this.holder.getSqlSession().close();
          }
          this.holder.reset();
        }
      }
    
    

    总结下getSqlSession方法:

    getSqlSession主要的目的就是获取一个SqlSession对象,主要分为下面两大情况:

    • 在Spring事务管理内,那么首次调用此方法,会绑定一个SqlSession对象到当前线程,供后续调用,而不用重新创建。随着Spring事务的提交方法被调用时会触发beforeCommit钩子而执行sqlSession.commit()以完成MyBatis的内部流程(缓存、批处理),或者是Spring事务的回滚方法。无论是提交还是回滚最后都会触发beforeCompletion以及afterCompletion钩子来解绑绑定的SqlSession以及close
    • Spring事务管理之外,每次都会返回一个新的SqlSessionSqlSession的提交、回滚、释放都会在每一个方法执行后得到应有的调用,在上面动态代理拦截逻辑中体现。

    再看closeSqlSession

    代码非常简单,不用做过多的解释。

    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        notNull(session, NO_SQL_SESSION_SPECIFIED);
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
            .getResource(sessionFactory);
        // 是Spring事务管理的,将获取次数减一
        if ((holder != null) && (holder.getSqlSession() == session)) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
            }
            holder.released();
        } else {
            // 否则直接释放
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
            }
            session.close();
        }
    }
    

    最后是isSqlSessionTransactional

    此方法就使用来判断给定的SqlSession对象是不是运行在Spring事务中,如果看懂了getSqlSession方法的话,也不用做过多解释了。

    public static boolean isSqlSessionTransactional(SqlSession session, 
                                                    SqlSessionFactory sessionFactory) {
        notNull(session, NO_SQL_SESSION_SPECIFIED);
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
            .getResource(sessionFactory);
        return (holder != null) && (holder.getSqlSession() == session);
    }
    

    至此SqlSessionTemplate中最核心的源码就全部分析完毕了,而类中其它方法都是通过这个代理SqlSession完成的。

    总结

    本文主要分析了SqlSessionTemplate这个类的实现原理,解释了这个类为什么是线程安全的,又是如何参与进Spring管理的事务中这两个最大的特征点。

  • 相关阅读:
    bash帮助文档简单学习;bash手册翻译
    jmeter的master、slave模型启动方法
    如何查看pip安装包的所有版本;以及ipython的安装
    简单过下bash/sh脚本基本知识吧
    Celery 启动报错 can_read() got an unexpected keyword argument timeout
    paramiko执行命令超时的问题
    远程的jmeter自动执行完,如何回调通知被调用者“结束”状态
    记录一下:关于mysql数据误删除恢复的问题
    数据库Sharding的基本思想和切分策略
    数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示
  • 原文地址:https://www.cnblogs.com/wt20/p/10963071.html
Copyright © 2020-2023  润新知