Spring整合MyBatis之SqlSession对象的产生
目录
- 前言
- `SqlSession` 对象的产生
- `SqlSessionTemplate` 类登场
- `SqlSessionTemplate` 类简介
- 对象 `sqlSessionProxy` 初始化
- 小结
- `SqlSessionTemplate` 的使用
- `Mapper` 映射文件
- 测试类
- 测试类的衍生
前言
在 上一篇 文章中,我们知道 MyBatis
有四大核心对象,分别是 SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession,Mapper
映射器,而 MyBatis
在和 Spring
整合之后,Spring
会帮助我们管理 SqlSessionFactory、SqlSession、Mapper
映射器这些核心 bean
;在 这一篇 文章中已经阐述了 SqlSessionFactory
对象的产生过程,在本篇文章中就是来阐述 SqlSession
这个对象是如何产生的
SqlSession
对象的产生
在 MyBatis
中我们获取 SqlSession
的实例,实际上使用的都是 new DefaultSqlSession()
的方式,源码在 DefaultSqlSessionFactory
类中如下
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);// MyBatis源码:使用的是 new DefaultSqlSession()的方式,返回一个 SqlSession 对象return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}
而 MyBatis
在和 Spring
整合之后,我们是不能使用 new DefaultSqlSession()
的方式来创建 SqlSession
的,因为 DefaultSqlSession
类在 MyBatis
中是线程不安全的
Spring
为了解决类 DefaultSqlSession
线程不安全的问题,为我们提供了一个线程安全的 SqlSessionTemplate
类
SqlSessionTemplate
类登场
SqlSessionTemplate
类简介
该类同我们在 MyBatais
中使用的 DefaultSqlSession
类相同,也都为我们提供了直接操作数据库的一些方法,比如:selectOne(),selectList(),insert(),update(),delete()
等
SqlSessionTemplate
是mybatis-spring
的核心。这个类负责管理MyBatis
的SqlSession
,调用MyBatis
的SQL
方法。SqlSessionTemplate
是线程安全的,可以被多个DAO
所共享使用SqlSessionTemplate
实现了SqlSession
接口,SqlSessionTemplate
通常是被用来替代默认的MyBatis
实现的DefaultSqlSession
, 因为模板可以参与到Spring
的事务中并且被多个注入的映射器类所使用时也是线程安全的
对象 sqlSessionProxy
初始化
我们从 SqlSessionTemplate
类的 selectOne()
方法入手来分析,发现此处使用的是 SqlSession
类型的 sqlSessionProxy
的一个对象,这个对象我们通过名称可以看出它是一个代理类
public class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;@Overridepublic <T> T selectOne(String statement) {// 使用 sqlSessionProxy 来调用 selectOne() 方法return this.sqlSessionProxy.<T> selectOne(statement);}// 部分代码省略......
}
那么 sqlSessionProxy
在哪里初始化呢?接着看源码如下
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;// 初始化 sqlSessionProxy 代理对象,基于JDK动态代理创建代理对象this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());
}
在 SqlSessionTemplate
类中共有 3
个构造方法,其最终内部都是调用上述的构造方法来完成初始化的。这个方法的关键是内部通过调用 JDK
动态代理 Proxy.newProxyInstance()
方法创建了一个 SqlSession
代理对象并赋值给了 sqlSessionProxy
;此时,我们需要把目光聚焦到 SqlSessionInterceptor
类的实现逻辑上,因为其中包含该代理对象的代理逻辑,其代码逻辑如下
private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 首先会使用工厂类、执行器类型、异常解析器创建一个 sqlSessionSqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {// 然后再调用sqlSession的实现类,实际上就是在这里调用了DefaultSqlSession的方法Object result = method.invoke(sqlSession, args);// 此处是通过 Transactional 事务的方式if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// sqlSession提交sqlSession.commit(true);}return result;} catch (Throwable t) {// 省略......}throw unwrapped;} finally {if (sqlSession != null) {// 关闭sqlSessioncloseSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
}
发现了没,在代理逻辑中先通过 getSqlSession()
方法获取 SqlSession
,而后执行 method.invoke()
方法,之后通过 isSqlSessionTransactional()
方法判断当前操作是否是事务操作,如果属于事务操作则执行 sqlSession.commit()
方法,以此完成了事务的提交
我们继续对 getSqlSession()
方法进行分析,代码如下
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);// 此处,通过事务管理器的 getResource()方法,将我们的sqlSessionFactory工厂类传入// 返回的是一个 SqlSessionHolder持久器类SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}if (LOGGER.isDebugEnabled()) {LOGGER.debug("Creating a new SqlSession");}session = sessionFactory.openSession(executorType);registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}
我们继续对 getResource()
方法进行分析,代码如下
public abstract class TransactionSynchronizationManager {private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");public static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);// 执行 doGetResource()方法Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");}return value;}private static Object doGetResource(Object actualKey) {// 我们通过 resources.get() 的方式来获得,我们对这个 resources 属性进行分析// 发现它返回的 SqlSession的持久器 SqlSessionHolder 也是一个ThreadLocal对象// 即:我们每一个获取到的 SqlSession都是有自己的SqlSessionHolder对象的// 此处,一个事务一个SqlSession。本身ThreadLocal 就是线程安全的,所以每一个SqlSession 也都是线程安全的Map<Object, Object> map = (Map)resources.get();if (map == null) {return null;} else {Object value = map.get(actualKey);if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {map.remove(actualKey);if (map.isEmpty()) {resources.remove();}value = null;}return value;}}
}
由此通过 SqlSessionTemplate
就可以创建线程安全的 SqlSession
对象了
小结
在 SqlSessionTemplate
类中有一个SqlSession
类型的 sqlSessionProxy
变量,他其实是 SqlSession
的代理类 SqlSessionInterceptor
- 在每次
SqlSessionTemplate
执行方法(增删改查)的时候,最后都给交给SqlSessionInterceptor
来代理执行 SqlSessionInterceptor
每次在获取SqlSession
的时候,都会使用事务管理器从Threadlocal
中获取,所以必然是线程安全的!对于Spring
而言,每个事务都会使用同一个SqlSession
,其实也就是DefaultSqlSession
,用于操作相应的executor
来进行db
交互
SqlSessionTemplate
的使用
Mapper
映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jorg.mybatis.mappers.StudentMapper"><update id="update" parameterType="java.util.Map">UPDATEstudentSET`name` = #{name}WHEREid = #{id}</update>
</mapper>
测试类
public class SqlSessionTemplateTest {public static void main(String[] args) {Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");// 获取 SqlSessionFactory 对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);HashMap<String, Object> paramMaps = Maps.newHashMap();paramMaps.put("name", "hello");paramMaps.put("id", "100");sqlSessionTemplate.update("update", paramMaps);}
}
- 在使用
SqlSessionTemplate
时,是无需通过调用commit()
方法就可以完成真正的更新。原因就在于SqlSessionInterceptor
类中会执行method.invoke()
方法,之后通过isSqlSessionTransactional()
方法判断当前操作是否是事务操作,如果属于事务操作则执行sqlSession.commit()
方法,以此完成了事务的提交
在日常开发中,应该很少以上述的方式来使用 MyBatis
,更多的则是将对应 Mapper
配置关联到相应的 interface
,并直接调用 interface
中定义的方法来操作数据库。如下所示
测试类的衍生
public class SqlSessionTemplateTest {public static void main(String[] args) {Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");// 获取 SqlSessionFactory 对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);studentMapper.update(100, "hello");}
}
上述通过 interface
来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?请看下回分解 点这里
Spring整合MyBatis之SqlSession对象的产生相关推荐
- Spring整合mybatis中的sqlSession是如何做到线程隔离的?
转载自 Spring整合mybatis中的sqlSession是如何做到线程隔离的? 项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按 ...
- Spring整合Mybatis之注解方式,(注解整合Junit)
Spring整合Mybatis之注解方式 我有一篇博客详细写了我自己使用xml的方法Spring整合MyBatis,现在我就把核心配置文件中的每个bean的配置使用注解的方式实现 注解整合MyBati ...
- spring 整合 mybatis 中数据源的几种配置方式
因为spring 整合mybatis的过程中, 有好几种整合方式,尤其是数据源那块,经常看到不一样的配置方式,总感觉有点乱,所以今天有空总结下. 一.采用org.mybatis.spring.mapp ...
- Spring 整合 Mybatis
数据库环境 // 创建mybatis数据库 create database mybatis;use mybatis // 创建teacher表 create table teacher(id int ...
- Spring整合MyBatis导致一级缓存失效问题
熟悉MyBatis的小伙伴都知道MyBatis默认开启一级缓存,当我们执行一条查询语句后,MyBatis会以我们查询的信息生成一个缓存key,查询的结果为value,存到一个map中,即存入一级缓存. ...
- Spring整合Mybatis之DAO层、Service层开发
3. Spring整合Mybatis编程DAO层开发 1. 项目引入相关依赖spring mybatis mysql mybatis-spring druid2. 编写spring.xml整合:spr ...
- Spring——Spring整合MyBatis
文章目录: 1.写在前面 2.实现步骤 2.1 项目的大体框架 2.2 使用Navicat在数据库中创建一张表student2 2.3 在pom.xml文件中加入maven依赖 2.4 编写实体类St ...
- 12干货!spring整合mybatis底层源码分析
核心代码 1.解析配置类上的@MapperScan("com.liqi.mapper") @Import(MapperScannerRegistrar.class) 会调用Mapp ...
- Spring 整合 Mybatis - 二(切面、事务管理)
紧接着上篇<Spring 整合 Mybatis - 一(基础)>,介绍Spring 整合 Mybatis的切面.事务管理. 1 增加切面aop功能 1.1 spring.xml sprin ...
- Spring 整合 Mybatis 原理
目录 Mybatis的基本工作原理 分析需要解决的问题 Spring中Bean的产生过程 解决问题 解决方案 FactoryBean Import 总结 优化 Mybatis的基本工作原理 在 Myb ...
最新文章
- mysql sql 语句事务_MySQL: 3、SQL语言 ②约束、事务
- queuetimer,如何使用CreateTimerQueueTimer建立在C#中高分辨率计时器?
- 在Tableau中去除选择高亮效果
- matlab深度学习_matlab使用贝叶斯优化的深度学习
- 6大设计原则之迪米特法则
- HBase BlockCache系列 - 探求BlockCache实现机制
- python中acosh_acosh()函数以及C ++中的示例
- begin.lydsy 入门OJ题库:1104:那些N位数
- ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏
- [leetcode]70. 爬楼梯
- 自己的HTML5 播放器
- python(3)-内置函数2
- informatic对表的增量抽取机制
- 联想g510升级换什么cpu好_联想G510笔记本完全拆机指南(图解)
- linux monitor工具,5 款 Ubuntu 系统监控工具
- HTML5实习手机端浏览器拍照和本地上传
- windows错误代码
- 电脑两个,电脑有两个系统盘怎么办
- 程序员如何站在巨人的肩膀上
- python爬去起点小说名以及评分
热门文章
- 从Logistic Regression 到 Neural Network
- 新浪推荐 二面 移动零
- 450.删除二叉搜索树中的节点
- 440.字典序中的第K小数字
- 1-n整数中1出现的次数
- Android Studio新建工程syncing失败;Android studio Connection timed out: connect
- iis启动服务时提示在本地计算机 无法启动iis admin服务,无法启动IIS Express Web服务器...
- 图的BFS和DFS原理及实例分析(java)
- http://blog.csdn.net/wangyoujin321/article/details/51472606
- python机器学习应用mooc_(2)Naive bayes