Mybatis SqlSessionTemplate 源码解析
Mybatis SqlSessionTemplate 源码解析
在使用 Mybatis 与 Spring 集成的时候我们用到了SqlSessionTemplate 这个类。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
通过源码我们何以看到 SqlSessionTemplate 实现了SqlSession 接口,也就是说我们可以使用SqlSessionTemplate 来代理以往的 DefailtSqlSession 完成对数据库的操作,但是DefailtSqlSession 这个类不是线程安全的,所以这个类不可以被设置成单例模式的。
如果是常规开发模式 我们每次在使用DefailtSqlSession 的时候都从 SqlSessionFactory 当中获取一个就可以了。但是与 Spring 集成以后,Spring 提供了一个全局唯一的 SqlSessionTemplate 示例 来完成DefailtSqlSession 的功能,问题就是:无论是多个 dao 使用一个 SqlSessionTemplate,还是一个 dao 使用一个 SqlSessionTemplate,SqlSessionTemplate 都是对应一个 sqlSession,当多个 web 线程调用同一个 dao 时,它们使用的是同一个 SqlSessionTemplate,也就是同一个 SqlSession,那么它是如何确保线程安全的呢?让我们一起来分析一下。
(1)首先,通过如下代码创建代理类,表示创建 SqlSessionFactory 的代理类的实例,该代理类实现 SqlSession 接口,定义了方法拦截器,如果调用代理类实例中实现 SqlSession 接口定义的方法,该调用则被导向 SqlSessionInterceptor 的 invoke 方法
1 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, 2 PersistenceExceptionTranslator exceptionTranslator) { 3 4 notNull(sqlSessionFactory, "Property'sqlSessionFactory'is required"); 5 notNull(executorType, "Property'executorType'is required"); 6 7 this.sqlSessionFactory = sqlSessionFactory; 8 this.executorType = executorType; 9 this.exceptionTranslator = exceptionTranslator; 10 this.sqlSessionProxy = (SqlSession) newProxyInstance( 11 SqlSessionFactory.class.getClassLoader(), 12 new Class[] { SqlSession.class }, 13 new SqlSessionInterceptor()); 14 }
核心代码就在 SqlSessionInterceptor 的 invoke 方法当中。
1 private class SqlSessionInterceptor implements InvocationHandler { 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 //获取 SqlSession(这个 SqlSession 才是真正使用的,它不是线程安全的) 4 //这个方法可以根据 Spring 的事物上下文来获取事物范围内的 sqlSession 5 //一会我们在分析这个方法 6 final SqlSession sqlSession = getSqlSession( 7 SqlSessionTemplate.this.sqlSessionFactory, 8 SqlSessionTemplate.this.executorType, 9 SqlSessionTemplate.this.exceptionTranslator); 10 try { 11 //调用真实 SqlSession 的方法 12 Object result = method.invoke(sqlSession, args); 13 //然后判断一下当前的 sqlSession 是否被 Spring 托管 如果未被 Spring 托管则自动 commit 14 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { 15 // force commit even on non-dirty sessions because some databases require 16 // a commit/rollback before calling close() 17 sqlSession.commit(true); 18 } 19 //返回执行结果 20 return result; 21 } catch (Throwable t) { 22 //如果出现异常则根据情况转换后抛出 23 Throwable unwrapped = unwrapThrowable(t); 24 if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { 25 Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); 26 if (translated != null) { 27 unwrapped = translated; 28 } 29 } 30 throw unwrapped; 31 } finally { 32 //关闭 sqlSession 33 //它会根据当前的 sqlSession 是否在 Spring 的事物上下文当中来执行具体的关闭动作 34 //如果 sqlSession 被 Spring 管理 则调用 holder.released(); 使计数器 -1 35 //否则才真正的关闭 sqlSession 36 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); 37 } 38 } 39 }
在上面的 invoke 方法当中使用了俩个工具方法 分别是
SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
那么这个俩个方法又是如何与 Spring 的事物进行关联的呢?
1 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { 2 //根据 sqlSessionFactory 从当前线程对应的资源 map 中获取 SqlSessionHolder,当 sqlSessionFactory 创建了 sqlSession,就会在事务管理器中添加一对映射:key 为 sqlSessionFactory,value 为 SqlSessionHolder,该类保存 sqlSession 及执行方式 3 SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 4 //如果 holder 不为空,且和当前事务同步 5 if (holder != null && holder.isSynchronizedWithTransaction()) { 6 //hodler 保存的执行类型和获取 SqlSession 的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个 sqlSessionFactory 创建的 sqlSession 会被重用 7 if (holder.getExecutorType() != executorType) { 8 throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 9 } 10 //增加该 holder, 也就是同一事务中同一个 sqlSessionFactory 创建的唯一 sqlSession,其引用数增加,被使用的次数增加 11 holder.requested(); 12 //返回 sqlSession 13 return holder.getSqlSession(); 14 } 15 //如果找不到,则根据执行类型构造一个新的 sqlSession 16 SqlSession session = sessionFactory.openSession(executorType); 17 //判断同步是否激活,只要 SpringTX 被激活,就是 true 18 if (isSynchronizationActive()) { 19 //加载环境变量,判断注册的事务管理器是否是 SpringManagedTransaction,也就是 Spring 管理事务 20 Environment environment = sessionFactory.getConfiguration().getEnvironment(); 21 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 22 //如果是,则将 sqlSession 加载进事务管理的本地线程缓存中 23 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 24 //以 sessionFactory 为 key,hodler 为 value,加入到 TransactionSynchronizationManager 管理的本地缓存 ThreadLocal<Map<Object, Object>> resources 中 25 bindResource(sessionFactory, holder); 26 //将 holder, sessionFactory 的同步加入本地线程缓存中 ThreadLocal<Set<TransactionSynchronization>> synchronizations 27 registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 28 //设置当前 holder 和当前事务同步 29 holder.setSynchronizedWithTransaction(true); 30 //增加引用数 31 holder.requested(); 32 } else { 33 if (getResource(environment.getDataSource()) == null) { 34 } else { 35 throw new TransientDataAccessResourceException( 36 "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 37 } 38 } 39 } else { 40 } 41 return session; 42 }
1 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 2 //其实下面就是判断 session 是否被 Spring 事务管理,如果管理就会得到 holder 3 SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 4 if ((holder != null)&& (holder.getSqlSession() == session)) { 5 //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 6 holder.released(); 7 } else { 8 //如果不是被 spring 管理,那么就不会被 Spring 去关闭回收,就需要自己 close 9 session.close(); 10 } 11 }
其实通过上面的代码我们可以看出 Mybatis 在很多地方都用到了代理模式,这个模式可以说是一种经典模式,其实不紧紧在 Mybatis 当中使用广泛,Spring 的事物,AOP ,连接池技术 等技术都使用了代理技术。在后面的文章中我们来分析 Spring 的抽象事物管理机制。