在使用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,
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方法当中。

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的事物进行关联的呢?

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

Mybatis-Spring SqlSessionTemplate 源码解析的更多相关文章

  1. Spring IoC源码解析之getBean

    一.实例化所有的非懒加载的单实例Bean 从org.springframework.context.support.AbstractApplicationContext#refresh方法开发,进入到 ...

  2. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  3. Spring IoC源码解析之invokeBeanFactoryPostProcessors

    一.Bean工厂的后置处理器 Bean工厂的后置处理器:BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)和BeanDefinitionRegistr ...

  4. Spring系列(五):Spring AOP源码解析

    一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...

  5. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  6. Spring系列(三):Spring IoC源码解析

    一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...

  7. Spring Boot系列(四):Spring Boot源码解析

    一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...

  8. Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程

    一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...

  9. Mybatis SqlSessionTemplate 源码解析

    As you may already know, to use MyBatis with Spring you need at least an SqlSessionFactory and at le ...

  10. Spring IoC源码解析——Bean的创建和初始化

    Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...

随机推荐

  1. MySQL之GROUP BY用法误解

    1.说明  “Group By”从字面意义上理解就是根据“By”指定的规则对数据进行分组,所谓的分组就是将一个“数据集”划分成若干个“小区域”,然后针对若干个“小区域”进行数据处理.(只是简单说明这个 ...

  2. HDU2093--考试排名

    考试排名 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submissi ...

  3. HTTP 报文中的 Header 字段进行身份验证

    [小技巧][ASP.Net MVC Hack] 使用 HTTP 报文中的 Header 字段进行身份验证 在一些 Web 系统中,身份验证是依靠硬件证书进行的:在电脑上插入 USB 证书,浏览器插件读 ...

  4. MY WAY程序(十八) 团队开发

    1.通信知识          要了解的强哥的移动通信先验知识布局.我和另外一个毕业生有自己的学习,但我真的没有认真看.了解了一下,其余大部分时间在搞nodejs.另外一个应届毕业生则是按着一本电子书 ...

  5. linux_shell 特殊符号的介绍

    linux_shell 特殊符号的介绍 2011-12-17 17:54:07 分类: 原文地址:linux_shell 特殊符号的介绍 作者:xu_liuzhen linux_shell 特殊符号的 ...

  6. 编写Windows Service 备忘

    项目需求要做一个定时扫表,将按条件查询到的数据插入或者更新到另一个数据表的需求,老大要求让用window service来做 因为以前没有做过,把这次的经历写出来.作为备忘. 1.什么是windows ...

  7. WCF、Web API、WCF REST、Web Service

    WCF.Web API.WCF REST.Web Service 区别 Web Service It is based on SOAP and return data in XML form. It ...

  8. 飘逸的python - 命令行漂亮的显示json数据

    之前做的一个脚本把信息用json格式存到文本中.这样的好处是简便,易编辑,并且拥有强大的表达能力. 不过从文本cat出来的是一堆很难看的字符串,没换行和缩进.这时候就在想,如果有个类似于IDE的格式化 ...

  9. ASP.NET开发的大型网站有哪些架构方式

    谈谈用ASP.NET开发的大型网站有哪些架构方式(成本) 在上篇文章里(http://www.cnblogs.com/ms0017/archive/2011/07/26/2117676.html),列 ...

  10. 利用Readability解决网页正文提取问题

    分享: 利用Readability解决网页正文提取问题   做数据抓取和分析的各位亲们, 有没有遇到下面的难题呢? - 如何从各式各样的网页中提取正文!? 虽然可以用SS为各种网站写脚本做解析, 但是 ...