mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?

一,DefaultSqlSession的线程不安全性

在MyBatis的架构中的SqlSession是提供给外层调用的顶层接口,实现类有:DefaultSqlSession,SqlSessionManager以及MyBatis的弹簧提供的实现SqlSessionTemplate默认的实现类为DefaultSqlSession如类图结构如下所示:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
对于MyBatis的提供的原生实现类来说,用的最多的就是DefaultSqlSession,但我们知道DefaultSqlSession这个类不是线程安全的如下!
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?

二,SqlSessionTemplate是如何使用DefaultSqlSession的

而在我们开发的时候肯定会用到spring,也会用到MyBatis的spring框架,在使用的MyBatis与spring集成的时候我们会用到了SqlSessionTemplate这个类,例如下边的配置,注入一个单例的SqlSessionTemplate对象:

   <!-- 为Mybatis创建SqlSessionFactory,同时指定数据源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--<property name="configLocation" value="classpath:META-INF/spring/mybatis-config.xml"/>-->
        <property name="mapperLocations" value="classpath*:com/store/user/rpc/service/dao/mapper/*Mapper.xml"/>
    </bean>

    <!-- 不加SqlSession不会关闭-->
    <bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"
          destroy-method="close" scope="prototype">
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>
123456789101112

SqlSessionTemplate的源代码注释如下:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
通过源码我们何以看到SqlSessionTemplate实现了SqlSession接口,也就是说我们可以使用SqlSessionTemplate来代理以前的DefaultSqlSession完成对数据库的操作,但是是DefaultSqlSession这个类不是线程安全的,所以DefaultSqlSession这个类不可以被设置成单例模式的。
如果是常规开发模式的话,我们每次在使用DefaultSqlSession的时候都从SqlSessionFactory对象当中获取一个就可以了但是与弹簧集成以后,弹簧提供了一个全局唯一的SqlSessionTemplate对象来完成DefaultSqlSession的功能,问题就是:无论是多个道使用一个SqlSessionTemplate,还是一个道使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个SQLSESSION对象,当多个网络线程调用同一个岛时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession的,那么它?是如何确保线程安全的呢让我们一起来分析一下:

三,SqlSessionTemplate是如何保证DefaultSqlSession线程安全的

(1)首先,通过如下代码创建代理类,表示创建的SqlSessionFactory的代理类的实例,该代理类实现的SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现的SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法(代理对象的InvocationHandler就是SqlSessionInterceptor,如果把它命名为SqlSessionInvocationHandler则更好理解!)核心
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
代码就在SqlSessionInterceptor的invoke方法当中。

    /**
   * Proxy needed to route MyBatis method calls to the proper SqlSession got
   * from Spring's Transaction Manager
   * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
   * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
   */
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          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) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
1234567891011121314151617181920212223242526272829303132333435363738394041

在上面的调用方法当中使用了两个工具方法分别是:

(1)SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
(2)SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
12

那么这两个方法又是如何与spring的事物进行关联的呢?
1,getSqlSession方法如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
                                           ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
        notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
        //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,
        // 当sqlSessionFactory创建了sqlSession,
        //就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,
        // 该类保存sqlSession及执行方式
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.
                getResource(sessionFactory);
        //从SqlSessionHolder中提取SqlSession对象
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }
        //如果当前事物管理器中获取不到SqlSessionHolder对象就重新创建一个
        session = sessionFactory.openSession(executorType);
        //将新创建的SqlSessionHolder对象注册到TransactionSynchronizationManager中
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
123456789101112131415161718192021222324

2,closeSqlSession方法如下:

/**
     * Checks if {@code SqlSession} passed as an argument is managed by Spring {@code TransactionSynchronizationManager}
     * If it is not, it closes it, otherwise it just updates the reference counter and
     * lets Spring call the close callback when the managed transaction ends
     *
     * @param session
     * @param sessionFactory
     */
    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder
        SqlSessionHolder holder = (SqlSessionHolder)
                TransactionSynchronizationManager.getResource(sessionFactory);
        if ((holder != null) && (holder.getSqlSession() == session)) {
            //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 
            holder.released();
        } else {
            //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close 
            session.close();
        }
    }
1234567891011121314151617181920

大致的分析到此为止,可能有些许不够顺畅,不过:纸上得来终觉浅,绝知此事要躬行还希望小伙伴打开自己的编译器,找到此处的代码,认真走一遍流程!
其实通过上面的代码我们可以看出的MyBatis在很多地方都用到了代理模式,代理模式可以说是一种经典模式,其实不紧紧在这个地方用到了代理模式,春天的事物,AOP,MyBatis的数据库连接池技术,MyBatis的的核心原理(如何在只有接口没有实现类的情况下完成数据库的操作!)等技术都使用了代理技术。

四,SqlSessionManager又是什么鬼?

上述说了一个SqlSession的实现还有一个SqlSessionManager,那么SqlSessionManager到底是什么个东西哪且看定义如下:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
你可能会发现SqlSessionManager的构造方法竟然是私人的,那我们怎么创建这个对象哪其实SqlSessionManager创建对象是通过的newInstance的方法创建对象的,但需要注意的是他虽然有私有的构造方法,并且提供给我们了一个公有的的newInstance方法,但它并不是一个单例模式!newInstance有很多重载的方法,如下所示:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
SqlSessionManager的openSession方法及其重载的方法是直接通过调用其中的底层封装的SqlSessionFactory对象的openSession方法来创建SqlSession对象的,重载方法如下:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?
SqlSessionManager中实现了SqlSession的接口中的方法,例如:选择,更新等,都是直接调用sqlSessionProxy代理对象中相应的方法在创建该代理对像的时候使用的InvocationHandler的对象是SqlSessionInterceptor,他是定义在SqlSessionManager的一个内部类,其定义如下:
mybatis(1) SqlSessionTemplate是如何保证的MyBatis中的SqlSession的线程安全的?

五,总结

综上所述,我们应该大致了解了DefaultSqlSession和SqlSessionManager之间的区别:
1,DefaultSqlSession的内部没有提供像SqlSessionManager一样通过ThreadLocal的的方式来保证线程的安全性;
2,SqlSessionManager是通过localSqlSession这个ThreadLocal的变量,记录与当前线程绑定的SqlSession的对象,供当前线程循环使用,从而避免在同一个线程多次创建的SqlSession对象造成的性能损耗;
3,DefaultSqlSession不是线程安全的,我们在进行原生开发的时候,需要每次为一个操作都创建一个SqlSession的对象,其性能可想而知;

上一篇:【IDEA集成Git】环境准备(ignore模板)


下一篇:CPU缓存:L1、L2 和 L3 缓存之间的区别