Mybatis sqlsession解析
一、sqlsession获取过程
1、基础配置
在mybatis框架下进行的数据库操作都需要首先获取sqlsession,在mybatis与spring集成后获取sqlsession需要用到sqlsessionTemplate这个类。
首先在spring对sqlsessionTemplate进行配置,使用到的是 org.mybatis.spring.SqlSessionTemplate 这个类。
<!-- SqlSession实例 --> <bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"destroy-method="close"> <!--当构造函数有多个参数时,可以使用constructor-arg标签的index属性,index属性的值从0开始,这里将sqlsessionFactory作为第一个参数传入--> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
<!-- 将数据源映射到sqlSessionFactory中 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:mybatis/mybatis-config.xml" /><property name="dataSource" ref="dataSource" /> </bean>
所以在sqlsessionTemplate的初始化过程中,首先会将sqlsessionFactory作为参数传入,sqlsessionFactory中映射了数据源信息。
配置事务,在后来的sqlsession获取过程中会对事务进行判断
<!--======= 事务配置 Begin ================= --> <!-- 事务管理器(由Spring管理MyBatis的事务) --> <bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 关联数据源 --><property name="dataSource" ref="dataSource"></property> </bean> <!-- 注解事务 --> <tx:annotation-driven transaction-manager="transactionManager" /> <!--======= 事务配置 End =================== -->
2、sqlsessionTemplate的初始化
public class SqlSessionTemplate implements SqlSession {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");Assert.notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());}
SqlsessionTemplate类的最开始初始化过程中,首先会通过sqlsessionFactory参数进行构造,通过Proxy.newProxyInstance()方法来创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlsessionTemplate的一个内部类SqlSessionInterceptor的invoke方法,最终初始化sqlsessionProxy。
3、sqlsession的调用过程
由于上面两个过程中已经将sqlsessionTemplate中的sqlsessionProxy已经初始化完毕,所以在代码中可以进行调用。调用最终都会进入SqlSessionInterceptor的invoke方法。
private class SqlSessionInterceptor implements InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)//这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSessionfinal SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {//调用真实SqlSession的方法Object result = method.invoke(sqlSession, args);//然后判断一下当前的sqlSession是否有配置Spring事务 如果没有自动commitif (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()sqlSession.commit(true);}//返回执行结果return result;} catch (Throwable t) {//如果出现异常则根据情况转换后抛出Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated != null) {unwrapped = translated;}}throw unwrapped;} finally {//关闭sqlSession//它会根据当前的sqlSession是否在Spring的事务上下文当中来执行具体的关闭动作//如果sqlSession被Spring事务管理 则调用holder.released(); 使计数器-1//否则才真正的关闭sqlSessionSqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
在上面的代码中用到两个很关键的方法:
获取sqlsession方法:SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
关闭sqlsession方法:SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式 SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); //如果holder不为空,且和当前事务同步 if (holder != null && holder.isSynchronizedWithTransaction()) { //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); } //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加 holder.requested(); //返回sqlSession return holder.getSqlSession(); } //如果找不到,则根据执行类型构造一个新的sqlSession SqlSession session = sessionFactory.openSession(executorType); //判断同步是否激活,只要SpringTX被激活,就是true if (isSynchronizationActive()) { //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务 Environment environment = sessionFactory.getConfiguration().getEnvironment(); if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { //如果是,则将sqlSession加载进事务管理的本地线程缓存中 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 bindResource(sessionFactory, holder); //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); //设置当前holder和当前事务同步 holder.setSynchronizedWithTransaction(true); //增加引用数 holder.requested(); } else { if (getResource(environment.getDataSource()) == null) { } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { } return session; }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 holder.released(); } else { //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close session.close(); } }
二、mybatis的缓存
1、一级缓存
Mybatis的一级缓存是默认开启的,主要是通过sqlsession来实现的,每个sqlsession对象会在本地创建一个缓存(local cache),对于每次查询都会在本地缓存中进行查询,如果命中则直接返回,如果没查到则进入到数据库中进行查找。一级缓存是sqlsession级别的。
从上面sqlsession的获取源码中可以看到,每次获取一个全新的sqlsession最终都是会保存在ThreadLocal中跟线程绑定,如果在spring中配置了事务则整个事务周期里面都共享一个sqlsession,如果没有配置事务则每次请求都是一个独立的sqlsession。每次执行完后数据库操作后,如果还在事务周期中只对sqlsession的引用次数减一,否则直接关闭sqlsession。
一级缓存执行的时序图:
小结:
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
2、二级缓存
在系统中如果需要使用二级缓存则直接在spring中进行配置声明即可。
<!-- 这个配置使全局的映射器启用或禁用 缓存 --> <setting name="cacheEnabled" value="true" /> <!-- 开启二级缓存开关 --> <cache/>
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。
转载于:https://www.cnblogs.com/kma-3/p/9957740.html
Mybatis sqlsession解析相关推荐
- 什么是Mybatis配置解析?(源码+图文)
什么是Mybatis配置解析?(源码+图文) 1. 核心配置文件 mybatis-config.xml configuration(配置)properties(属性)settings(设置)typeA ...
- Java-Mybatis(二): Mybatis配置解析、resultMap结果集映射、日志、分页、注解开发、Mybatis执行流程分析
Java-Mybatis-02 学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1NE411Q7Nx 学习资料:mybatis 参考文档 – ht ...
- mybatis配置文件解析
mybatis配置文件解析 mybatis核心配置文件`mybatis-config.xml文件. mybatis的配置文件包含了会深深影响mybatis行为的设置和属性信息. 能配置的内容: con ...
- 【Springboot+mybatis】 解析Excel并批量导入到数据库
[Springboot+mybatis] 解析Excel并批量导入到数据库 置顶 2018年01月16日 20:05:52 冉野丶 阅读数:4060 标签: excel导入数据库 文件上传 excel ...
- mybatis collection解析以及和association的区别
说到mybatis的collection标签,我们肯定不陌生,可以通过它解决一对多的映射问题,举个例子一个用户对应多个系统权限,通过对用户表和权限表的关联查询我们可以得到好多条记录,但是用户信息这部分 ...
- MyBatis原理解析
参考自: MyBatis原理分析(通俗易懂)_Coder Wang-CSDN博客_mybatis原理 从源码一层一层进行分析,写得清晰易懂.我再这里就不赘述了,就写一下自己的大白话总结吧. Mybat ...
- Mybatis架构解析
目录 整体架构层 SqlSession初始化 SQL请求的执行和分发 执行器层和缓存 ps:mybatis 有很多不同的用法,不同的用法对应相同或不同类的不同方法,走不同的分支逻辑,但是它们终究都还是 ...
- Mybatis的解析模块基础
1. XPathParser mybatis的基于配置文件的解析是基于XPathParser,在解析String类型时,会通过PropertyParser来做的,在解析时结合GenericTokenP ...
- mybatis配置文件解析错误解决方法
2019独角兽企业重金招聘Python工程师标准>>> 转:mybatis写mapper文件注意事项 mybatis3 mapper cdata mybatis3 null xml中 ...
- MyBatis→SqlSession、sqlMapConfig.xml、映射XML文件、OGNL、拼接SQL标签、取值查值、批量SQL、一对多多对一多对多
官网 https://mybatis.org/mybatis-3/ SqlSession sqlMapConfig.xml OGNL XML拼接SQL标签 参数取值 预编译与直接赋值 编码规范 sel ...
最新文章
- 一些来自STL的好东西
- 基础省选+NOI-第2部分 数据结构进阶(II)
- VIM 下工程的管理工具
- 不受支持的SQL类型1111
- shell变量、函数和数组以及字符串的截取
- 死亡之ping (ping of death)
- 人工智能(regex)—— 正则表达式
- Beautiful Soup
- 中学校长在开学典礼上的讲话
- ThinkPHP5.0+七牛云SDK文件上传
- idea双击打不开我的解决方法
- 【FPGA】Spartan®-7器件XC7S75-1FGGA484C、XC7S15-1FTGB196C现场可编程门阵列芯片
- Java实现简单的KFC点餐系统(未实现可视化)
- Android Manifest
- 最小点权覆盖集最大点权独立集
- 我是如何提高自己的「编程能力」的?
- 在Android上用PHP编写应用- PFA初探
- 每日一笑20200529
- 从个人到创业团队都能用的项目开发部署流程!建议收藏!
- 什么是a站、b站、c站、d站、e站、f站、g站、h站、i站、j站、k站、l站、m站、n站?