Mybatis-Spring 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,
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 源码解析的更多相关文章
- Spring IoC源码解析之getBean
一.实例化所有的非懒加载的单实例Bean 从org.springframework.context.support.AbstractApplicationContext#refresh方法开发,进入到 ...
- spring事务源码解析
前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...
- Spring IoC源码解析之invokeBeanFactoryPostProcessors
一.Bean工厂的后置处理器 Bean工厂的后置处理器:BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)和BeanDefinitionRegistr ...
- Spring系列(五):Spring AOP源码解析
一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...
- Spring系列(六):Spring事务源码解析
一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...
- Spring系列(三):Spring IoC源码解析
一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...
- Spring Boot系列(四):Spring Boot源码解析
一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...
- Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程
一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...
- Mybatis SqlSessionTemplate 源码解析
As you may already know, to use MyBatis with Spring you need at least an SqlSessionFactory and at le ...
- Spring IoC源码解析——Bean的创建和初始化
Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...
随机推荐
- 从一道数学题弹程序员的思维:数学题,求证:(a+b%c)%c=(a+b)%c
在学校论坛看到这道题目,全忘了的感觉. 如果你是高中的,那我觉得你完全没问题.但是,在这个博客园的圈子,觉得全部人都是程(ban)序(zhuan)员(gong)相关的人员,解决这个问题有点难度,毕竟, ...
- curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set in
当系统开启safe_mode和 open_basedir,在程序中使用以下语句 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); 并且遇到301,302状态 ...
- sql点滴42—mysql中的数据结构
原文:sql点滴42-mysql中的数据结构 MySQL 的数值数据类型可以大致划分为两个类别,一个是整数,另一个是浮点数或小数.许多不同的子类型对这些类别中的每一个都是可用的,每个子类型支持不同大小 ...
- web.xml运行序列总结
在整个订单<context-param>--<listener>--<filter>--<servlet>. 其中,内的各类别中的序列被运行.和< ...
- Python框架之Django学习
当前标签: Django Python框架之Django学习笔记(十四) 尛鱼 2014-10-12 13:55 阅读:173 评论:0 Python框架之Django学习笔记(十三) 尛 ...
- hadoop编程技巧(3)---定义自己的区划类别Partitioner
Hadoop代码测试环境:Hadoop2.4 原则:在Hadoop的MapReduce过程.Mapper阅读过程完成后数据.它将数据发送到Partitioner.由Partitioner每个记录应当采 ...
- 【转】 教你如何创建类似QQ的android弹出菜单
原文地址:http://www.apkbus.com/android-18034-1-1.html 大家可能看到android的自带的系统菜单比较难看,如图: 2011-12-4 23:13 上传 下 ...
- SQL Server查看表结构及视图,适合开发者使用,简单易用
SELECT * FROM INFORMATION_SCHEMA.TABLES SELECT * FROM INFORMATION_SCHEMA.COLUMNS 查看执行结果
- Ibatis.Net执行Sql超时commandTimeout的一个坑
项目中使用了Ibatis.Net,数据库是Mysql,在做一个批量Update的操作时,需要执行40几秒,在执行到30秒的时候,会抛出异常:Timeout expired , The timeout ...
- Aforge.net 一个专门为开发者和研究者基于C#框架设计
时间过得真快啊,转眼今年就要过去了,大半年都没有写博客了,要说时间嘛,花在泡妹子和搞英语去了,哈哈...前几天老大问我 怎么这么长时间都没写博客了,好吧,继续坚持,继续分享我的心得体会. 这个系列我们 ...