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的抽象事物管理机制。

http://www.cnblogs.com/daxin/p/3544188.html

时间: 2024-10-11 20:43:10

Mybatis SqlSessionTemplate 源码解析的相关文章

MyBatis 3源码解析(一)

Mybatis是支持定制化SQL.存储过程和高级映射的持久层框架.主要完成两件事: 封装JDBC的操作 利用反射完成Java类和SQL之间的转换 mybatis的主要目的就是管理执行SQL是参数的输入和输出,编写SQL和结果集的映射是mybatis的主要优点 mybatis中主要类和接口 Configuration:将mybatis配置文件中的信息保存到该类中 SqlSessionFactory:解析Configuration类中的配置信息,获取SqlSession SqlSession:负责和

MyBatis 3源码解析(二)

MapperProxy 到了这了大家可能还有一个疑问,我调用的是DAO接口中的方法,和上面这些好像没关系.别急接下来我们就来看看二者是怎么联系起来的 在mybatis和Spring集合使用中,使用DAO时我们一般使用@Autowired注入,但是大家有没有一个疑问,DAO是一个接口,接口是不能创建对象的,这个是怎么完成的呢? Mybatis获取如何获取Mapper? 先来看第一个疑惑:使用mybatis操作数据库时,方法是这样的: SqlSessionFactory sessionFactory

MapperScannerConfigurer源码解析

声明:源码基于mybatis-spring 1.3.2 前文 首先在阅读本文前需要明白整合后的使用方式以及熟悉MyBatis本身的工作原理,再者如果对于本文相关知识点不熟悉的可以参考下述文章. MyBatis与Spring整合 SqlSessionTemplate源码解析 Spring包扫描机制详解 前言 一般在项目中使用MyBatis时,都会和Spring整合一起使用,通过注入一个Mapper接口来操纵数据库.其中的原理就是使用了MyBatis-Spring的MapperScannerConf

Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor

Mybatis源码解析(一)(2015年06月11日)

一.简介 先看看Mybatis的源码结构图,Mybatis3.2.7版本包含的包共计19个,其他版本可能会少. 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得,而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例.

mybatis源码-解析配置文件(三)之配置文件Configuration解析(超详细, 值得收藏)

1. 简介 1.1 系列内容 本系列文章讲解的是mybatis解析配置文件内部的逻辑, 即 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 其背后的逻辑. 1.2 适合对象 了解如何使用 mybatis 来访问数据库.可参看<

Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao明明是我们定义的一个接口类,根本没有定义实现类,那这个userMapper是什么?是mybatis自动为我们生成的实现类吗? 为了更好的解释着两个问题,我们需要重新认识Configuration这个类. 但是在这之前,你需要了解一个概念(设计模式):JAVA设计模式-动态代理(Proxy)示例及说明

mybatis源码-解析配置文件(四)之配置文件Mapper解析

在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的一部分配置, 在本文章中, 我们讲解解析mappers节点, 即 xxxMapper.xml 文件的解析. 1 解析入口 在解析 mybatis-config.xml 时, 会进行解析 xxxMapper.xml 的文件. 在图示流程的 XMLConfigBuilder.parse() 函数中, 该

mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

相关文章推荐 mybatis 缓存的使用, 看这篇就够了 mybatis源码-解析配置文件(四)之配置文件Mapper解析 1. 简介 本文章主要讲解的是, xxxMapper.xml 文件中, cache 节点的源码. 2. 解析 XMLMapperBuilder.cacheElement() 方法主要负责解析 <cache> private void cacheElement(XNode context) throws Exception { if (context != null) {