目录

  • 前言
  • `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()

  • SqlSessionTemplatemybatis-spring 的核心。这个类负责管理 MyBatisSqlSession,调用 MyBatisSQL 方法。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对象的产生相关推荐

  1. Spring整合mybatis中的sqlSession是如何做到线程隔离的?

    转载自  Spring整合mybatis中的sqlSession是如何做到线程隔离的? 项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按 ...

  2. Spring整合Mybatis之注解方式,(注解整合Junit)

    Spring整合Mybatis之注解方式 我有一篇博客详细写了我自己使用xml的方法Spring整合MyBatis,现在我就把核心配置文件中的每个bean的配置使用注解的方式实现 注解整合MyBati ...

  3. spring 整合 mybatis 中数据源的几种配置方式

    因为spring 整合mybatis的过程中, 有好几种整合方式,尤其是数据源那块,经常看到不一样的配置方式,总感觉有点乱,所以今天有空总结下. 一.采用org.mybatis.spring.mapp ...

  4. Spring 整合 Mybatis

    数据库环境 // 创建mybatis数据库 create database mybatis;use mybatis // 创建teacher表 create table teacher(id int ...

  5. Spring整合MyBatis导致一级缓存失效问题

    熟悉MyBatis的小伙伴都知道MyBatis默认开启一级缓存,当我们执行一条查询语句后,MyBatis会以我们查询的信息生成一个缓存key,查询的结果为value,存到一个map中,即存入一级缓存. ...

  6. Spring整合Mybatis之DAO层、Service层开发

    3. Spring整合Mybatis编程DAO层开发 1. 项目引入相关依赖spring mybatis mysql mybatis-spring druid2. 编写spring.xml整合:spr ...

  7. Spring——Spring整合MyBatis

    文章目录: 1.写在前面 2.实现步骤 2.1 项目的大体框架 2.2 使用Navicat在数据库中创建一张表student2 2.3 在pom.xml文件中加入maven依赖 2.4 编写实体类St ...

  8. 12干货!spring整合mybatis底层源码分析

    核心代码 1.解析配置类上的@MapperScan("com.liqi.mapper") @Import(MapperScannerRegistrar.class) 会调用Mapp ...

  9. Spring 整合 Mybatis - 二(切面、事务管理)

    紧接着上篇<Spring 整合 Mybatis - 一(基础)>,介绍Spring 整合 Mybatis的切面.事务管理. 1 增加切面aop功能 1.1 spring.xml sprin ...

  10. Spring 整合 Mybatis 原理

    目录 Mybatis的基本工作原理 分析需要解决的问题 Spring中Bean的产生过程 解决问题 解决方案 FactoryBean Import 总结 优化 Mybatis的基本工作原理 在 Myb ...

最新文章

  1. mysql sql 语句事务_MySQL: 3、SQL语言 ②约束、事务
  2. queuetimer,如何使用CreateTimerQueueTimer建立在C#中高分辨率计时器?
  3. 在Tableau中去除选择高亮效果
  4. matlab深度学习_matlab使用贝叶斯优化的深度学习
  5. 6大设计原则之迪米特法则
  6. HBase BlockCache系列 - 探求BlockCache实现机制
  7. python中acosh_acosh()函数以及C ++中的示例
  8. begin.lydsy 入门OJ题库:1104:那些N位数
  9. ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏
  10. [leetcode]70. 爬楼梯
  11. 自己的HTML5 播放器
  12. python(3)-内置函数2
  13. informatic对表的增量抽取机制
  14. 联想g510升级换什么cpu好_联想G510笔记本完全拆机指南(图解)
  15. linux monitor工具,5 款 Ubuntu 系统监控工具
  16. HTML5实习手机端浏览器拍照和本地上传
  17. windows错误代码
  18. 电脑两个,电脑有两个系统盘怎么办
  19. 程序员如何站在巨人的肩膀上
  20. python爬去起点小说名以及评分

热门文章

  1. 从Logistic Regression 到 Neural Network
  2. 新浪推荐 二面 移动零
  3. 450.删除二叉搜索树中的节点
  4. 440.字典序中的第K小数字
  5. 1-n整数中1出现的次数
  6. Android Studio新建工程syncing失败;Android studio Connection timed out: connect
  7. iis启动服务时提示在本地计算机 无法启动iis admin服务,无法启动IIS Express Web服务器...
  8. 图的BFS和DFS原理及实例分析(java)
  9. http://blog.csdn.net/wangyoujin321/article/details/51472606
  10. python机器学习应用mooc_(2)Naive bayes