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方法
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
核心代码就在 SqlSessionInterceptor的invoke方法当中。
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
//这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
//一会我们在分析这个方法
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//调用真实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) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
//关闭sqlSession
//它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动作
//如果sqlSession被Spring管理 则调用holder.released(); 使计数器-1
//否则才真正的关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
在上面的invoke方法当中使用了俩个工具方法 分别是
SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
那么这个俩个方法又是如何与Spring的事物进行关联的呢?
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
//根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式
SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
//如果holder不为空,且和当前事务同步
if (holder != null && holder.isSynchronizedWithTransaction()) {
//hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
//增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加
holder.requested();
//返回sqlSession
return holder.getSqlSession();
}
//如果找不到,则根据执行类型构造一个新的sqlSession
SqlSession session = sessionFactory.openSession(executorType);
//判断同步是否**,只要SpringTX被**,就是true
if (isSynchronizationActive()) {
//加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
//如果是,则将sqlSession加载进事务管理的本地线程缓存中
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中
bindResource(sessionFactory, holder);
//将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations
registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
//设置当前holder和当前事务同步
holder.setSynchronizedWithTransaction(true);
//增加引用数
holder.requested();
} else {
if (getResource(environment.getDataSource()) == null) {
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
}
return session;
}
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
//其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder
SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
//这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用
holder.released();
} else {
//如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close
session.close();
}
}
其实通过上面的代码我们可以看出 Mybatis在很多地方都用到了代理模式,这个模式可以说是一种经典模式,其实不紧紧在Mybatis当中使用广泛,Spring的事物,AOP ,连接池技术 等技术都使用了代理技术。在后面的文章中我们来分析Spring的抽象事物管理机制。
上一篇: mybatis源码解析
推荐阅读
-
Mybaits 源码解析 (八)----- 全网最详细,没有之一:结果集 ResultSet 自动映射成实体类对象(上篇)
-
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析
-
asp.net abp模块化开发之通用树2:设计思路及源码解析
-
Mybaits 源码解析 (六)----- 全网最详细:Select 语句的执行过程分析(上篇)(Mapper方法是如何调用到XML中的SQL的?)
-
Java中的容器(集合)之ArrayList源码解析
-
vuex 源码解析(四) mutation 详解
-
严蔚敏数据结构源码及习题解析
-
死磕 java同步系列之CyclicBarrier源码解析——有图有真相
-
spring源码深度解析— IOC 之 默认标签解析(上)
-
spring源码深度解析— IOC 之 循环依赖处理