在MyBatis架构中SqlSession是提供给外层调用的顶层接口,实现类有:DefaultSqlSession、SqlSessionManager以及mybatis-spring提供的实现SqlSessionTemplate。默认的实现类为DefaultSqlSession如。类图结构以下所示:
对于MyBatis提供的原生实现类来讲,用的最多的就是DefaultSqlSession,但咱们知道DefaultSqlSession这个类不是线程安全的!以下:
web
而在咱们开发的时候确定会用到Spring,也会用到mybatis-spring框架,在使用MyBatis与Spring集成的时候咱们会用到了SqlSessionTemplate这个类,例以下边的配置,注入一个单例的SqlSessionTemplate对象:面试
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory" /> </bean>
SqlSessionTemplate的源代码注释以下:
经过源码咱们何以看到 SqlSessionTemplate实现了SqlSession接口,也就是说咱们可使用SqlSessionTemplate来代理以往的DefaultSqlSession完成对数据库的操做,可是DefaultSqlSession这个类不是线程安全的,因此DefaultSqlSession这个类不能够被设置成单例模式的。spring
若是是常规开发模式的话,咱们每次在使用DefaultSqlSession的时候都从SqlSessionFactory当中获取一个就能够了。可是与Spring集成之后,Spring提供了一个全局惟一的SqlSessionTemplate对象来完成DefaultSqlSession的功能,问题就是:不管是多个Dao使用一个SqlSessionTemplate,仍是一个Dao使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个sqlSession对象,当多个web线程调用同一个Dao时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession,那么它是如何确保线程安全的呢?让咱们一块儿来分析一下:sql
(1)首先,经过以下代码建立代理类,表示建立SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,若是调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法(代理对象的InvocationHandler就是SqlSessionInterceptor,若是把它命名为SqlSessionInvocationHandler则更好理解!)
核心代码就在 SqlSessionInterceptor的invoke方法当中。数据库
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的) //这个方法能够根据Spring的事务上下文来获取事物范围内的sqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //调用从Spring的事物上下文获取事物范围内的sqlSession对象 Object result = method.invoke(sqlSession, args); //而后判断一下当前的sqlSession是否被Spring托管 若是未被Spring托管则自动commit 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) { //关闭sqlSession,它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动做 //若是sqlSession被Spring管理 则调用holder.released(); //使计数器-1,不然才真正的关闭sqlSession closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
在上面的invoke方法当中使用了两个工具方法分别是:安全
(1)SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) (2)SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
那么这两个方法又是如何与Spring的事物进行关联的呢?session
一、getSqlSession方法以下:mybatis
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; }
二、closeSqlSession方法以下:架构
//删除了部分日志代码 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(); } }
大体的分析到此为止,可能有些许不够顺畅,不过:纸上得来终觉浅,绝知此事要躬行!还但愿小伙伴打开本身的编译器,找到此处的代码,认真走一遍流程!app
其实经过上面的代码咱们能够看出Mybatis在不少地方都用到了代理模式,代理模式能够说是一种经典模式,其实不牢牢在这个地方用到了代理模式,Spring的事物、AOP、Mybatis数据库链接池技术、MyBatis的核心原理(如何在只有接口没有实现类的状况下完成数据库的操做!)等技术都使用了代理技术。
上述说了SqlSession的实现还有一个SqlSessionManager,那么SqlSessionManager究竟是什么个东西哪?且看定义以下:
你可能会发现SqlSessionManager的构造方法居然是private的,那咱们怎么建立这个对象哪?其实SqlSessionManager建立对象是经过newInstance的方法建立对象的,但须要注意的是他虽然有私有的构造方法,而且提供给咱们了一个公有的newInstance方法,但它并非一个单例模式!
newInstance有不少重载的方法,以下所示:
SqlSessionManager的openSession方法及其重载的方法是直接经过调用其中底层封装的SqlSessionFactory对象的openSession方法来建立SqlSession对象的,重载方法以下:
SqlSessionManager中实现了SqlSession接口中的方法,例如:select、update等,都是直接调用sqlSessionProxy代理对象中相应的方法。在建立该代理对像的时候使用的InvocationHandler对象是SqlSessionInterceptor,他是定义在SqlSessionManager的一个内部类,其定义以下:
综上所述,咱们应该大体了解了DefaultSqlSession和SqlSessionManager之间的区别:
一、DefaultSqlSession的内部没有提供像SqlSessionManager同样经过ThreadLocal的方式来保证线程的安全性;
二、SqlSessionManager是经过localSqlSession这个ThreadLocal变量,记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一个线程屡次建立SqlSession对象形成的性能损耗;
三、DefaultSqlSession不是线程安全的,咱们在进行原生开发的时候,须要每次为一个操做都建立一个SqlSession对象,其性能可想而知;
那么问题来了:
一、为何mybatis-spring框架中不直接使用线程安全的SqlSessionManager(SqlSessionFactory它是线程安全的)而是使用DefaultSqlSession这个线程不安全的类,并经过动态代理的方式来保证DefaultSqlSession操做的线程安全性哪?
二、DefaultSqlSession中是如何经过Executor来表现策略模式的或者DefaultSqlSession如何使用策略模式模式的?