主题

  分享记录一下MyBatis的一级缓存相关的学习.

Demo

     public static void firstLevelCache() {
init("mybatis-config.xml"); SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class);
User u = mapper.selectByPrimaryKey(1);
System.out.println(u); User u2 = mapper.selectByPrimaryKey(1);
System.out.println(u2);
session.close(); System.out.println("==============session2=============="); SqlSession session2 = sqlSessionFactory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User u3 = mapper2.selectByPrimaryKey(1);
System.out.println(u3);
session2.close(); }

从1个Demo来学习.

上面这段代码首先通过session获取了mapper然后执行了2次同样的查询,查询用户ID为1的数据, 然后打印=======session2======,然后新开了一个session2. 然后获取了第二个mapper并执行了相同的查询.

输出结果:

2018-09-29 19:06:26,813 DEBUG [main] logging.LogFactory : Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2018-09-29 19:06:26,961 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:27,102 DEBUG [main] jdbc.JdbcTransaction : Opening JDBC Connection
Sat Sep 29 19:06:27 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2018-09-29 19:06:28,095 DEBUG [main] pooled.PooledDataSource : Created connection 1682463303.
2018-09-29 19:06:28,095 DEBUG [main] jdbc.JdbcTransaction : Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,108 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Preparing: select id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del from user where id = ?
2018-09-29 19:06:28,145 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Parameters: 1(Integer)
2018-09-29 19:06:28,172 DEBUG [main] UserMapper.selectByPrimaryKey : <== Total: 1
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
2018-09-29 19:06:28,172 DEBUG [main] jdbc.JdbcTransaction : Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,183 DEBUG [main] pooled.PooledDataSource : Returned connection 1682463303 to pool.
==============session2==============
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Opening JDBC Connection
2018-09-29 19:06:28,183 DEBUG [main] pooled.PooledDataSource : Checked out connection 1682463303 from pool.
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,193 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Preparing: select id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del from user where id = ?
2018-09-29 19:06:28,194 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Parameters: 1(Integer)
2018-09-29 19:06:28,210 DEBUG [main] UserMapper.selectByPrimaryKey : <== Total: 1
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
2018-09-29 19:06:28,210 DEBUG [main] jdbc.JdbcTransaction : Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,221 DEBUG [main] jdbc.JdbcTransaction : Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,221 DEBUG [main] pooled.PooledDataSource : Returned connection 1682463303 to pool.

从输出中我们可以看到,红色部分是执行的SQL,蓝色部分是查询到用户信息并打印的数据...

总共执行了3次输出打印,但是SQL查询命令只运行了2次.其中第1个session的第1次查询和第2个session的第1次查询的时候会触发SQL的执行.而第1个session的第二次查询没有处罚SQL执行

从中我们可以得到结论:

一级缓存是发生在同一个SqlSession中的, 1个sqlSession 多次执行相同的SQL查询会有缓存

如何缓存

然后我们来学习下MyBatis是如何设计二级缓存的.

总体来说我觉得最核心的就是BaseExecutor这个类

看到BaseExecutor的成员域中有个wrapper字段,类型是Executor类,大家就应该能明白,Executor是一种装饰者的设计模式.这种模式在mybatis里用的真的挺多的.

我们不管是自定义Mapper还是使用SqlSession去执行SQL,最终都是委托Executor来执行的.

如上图,我们的selectByPrimaryKey方法会调用SqlSession的selectOne然后再转到selectList,最后委托给executor来执行.

而默认的情况下,我们在从SqlSessionFactory里拿到的SqlSession的时候new的是一个SimpleExecutor外层包裹着CachingExecutor.

其中CachingExecutor涉及二级缓存,而BaseExecutor(SimpleExecutor的父类)主要涉及一级缓存.

从之前的BaseExecutor的结构图中我们也可以发现有1个叫做localCache的字段.这个字段类型是PerpetualCache

PerpetualCache可以认为就是hashmap的简单封装.

     @SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

当执行BaseExecutor的query方法的时候,如上代码所示,会先在L14那行,试着从localCache中获取,如果没有就L18,queryFromDatabase.否则就会返回localCache.getObject(key)的结果.代码还是比较清楚明了的

那么这里就涉及到一个问题.怎么样才算缓存命中? 或者换句话说CacheKey怎么计算? 因为相同的cacheKey会取到相同的缓存结果.

-1233329154:3360697231:test.mapper.UserMapper.selectByPrimaryKey:0:2147483647:select

    id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at,
updated_at, del from user
where id = ?:1:development

上面这段字符串就是我debug中截取的一个CacheKey的字符串形式. CacheKey的equals和toString依赖的成员域差不多.从toString的结果上来看可能更直观一点.

首先需要说明一下,CacheKey有一个doUpdate方法,这个方法允许你加入一些需要计算缓存key的成分.也就是说加入的对象会影响key的结果.

然后我们来分析一下那个字符串,其中有很多个组成部分

-1233329154为CacheKey本身的hashcode

3360697231为doUpdate里添加过的对象的hashcode和

后面的各个部分是doUpdate中的添加过的对象的各自的toString.那么可以添加哪些东西呢?

从字符串中其实也可以看出来.

类.方法名 + 分页初始位置(默认为0) + 分页终止位置(默认为Integer.MAX_VALUE) + SQL + 调用Mapper方法的参数 + 环境名(configuration里配置的,默认是development)

从代码里也可以看出就是这么个逻辑: 下图为BaseExecutor实现Executor接口生成cacheKey的方法

另外cacheKey的equals只是比toString多了一个count对象的校验,也就是doUpdate添加过了几个对象的校验...其他部分基本是一致的.

所以如果2次查询CacheKey的hashcode和equals一致,那么就会使用之前缓存的结果.

小结

MyBatis一级缓存主要是在BaseExecutor中实现的,CacheKey涉及到很多组成部分(hashcode+各部分hashcode+类.方法名+分页界限+SQL+参数+环境等), 从直观上的感受来说.调用同一个Mapper方法,如果参数一致,那就会被取到缓存结果.

MyBatis 学习记录4 MyBatis的一级缓存的更多相关文章

  1. MyBatis 学习记录5 MyBatis的二级缓存

    主题 之前学习了一下MyBatis的一级缓存,主要涉及到BaseExecutor这个类. 现在准备学习记录下MyBatis二级缓存. 配置二级缓存与初始化发生的事情 首先二级缓存默认是不开启的,需要自 ...

  2. myBatis学习(9):一级缓存和二级缓存

    正如大多数持久层框架一样,MyBatis同样提供了一级缓存和二级缓存的支持 1. MyBatis一级缓存基于PerpetualCache的HashMap本地缓存,其存储作用域为 Session,默认情 ...

  3. mybatis学习记录二——mybatis开发dao的方法

    4.1     SqlSession使用范围 4.1.1     SqlSessionFactoryBuilder 通过SqlSessionFactoryBuilder创建会话工厂SqlSession ...

  4. MyBatis学习总结(七)——Mybatis缓存(转载)

      孤傲苍狼 只为成功找方法,不为失败找借口! MyBatis学习总结(七)--Mybatis缓存 一.MyBatis缓存介绍 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的 ...

  5. 【转】MyBatis学习总结(七)——Mybatis缓存

    [转]MyBatis学习总结(七)——Mybatis缓存 一.MyBatis缓存介绍 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 一级缓存: 基于PerpetualC ...

  6. 【转】MyBatis学习总结(一)——MyBatis快速入门

    [转]MyBatis学习总结(一)——MyBatis快速入门 一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC ...

  7. 转:MyBatis学习总结(Mybatis总结精华文章)

    http://www.cnblogs.com/xdp-gacl/tag/MyBatis%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/ 当前标签: MyBatis学习总结   ...

  8. Mybatis学习(五)————— 延迟加载和缓存机制(一级二级缓存)

    一.延迟加载 延迟加载就是懒加载,先去查询主表信息,如果用到从表的数据的话,再去查询从表的信息,也就是如果没用到从表的数据的话,就不查询从表的信息.所以这就是突出了懒这个特点.真是懒啊. Mybati ...

  9. Mybatis学习记录(七)----Mybatis查询缓存

    1. 什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造 sql ...

随机推荐

  1. HihoCoder 1063 : 缩地 树形DP第二题(对象 边)

    时间限制:12000ms 单点时限:1000ms 内存限制:256MB 描述 编织者是 Dota 系列中的一个伪核,拥有很强的生存能力和线上消耗能力.编织者的代表性技能是缩地.缩地带来的隐身.极限移动 ...

  2. GraphQL和RESTful的区别

    GraphQL和RESTful的区别 http://graphql.cn/learn/ https://www.cnblogs.com/Wolfmanlq/p/9094418.html http:// ...

  3. 实现一个web服务器, 支持php

    暂时还很不完善, 不过框架已经写出来了. https://github.com/tw1996/studyHttpd/

  4. C#飞行棋游戏

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  5. matplotlib ----- 颜色, 标记样式

    颜色: 蓝色 - 'b' 绿色 - 'g' 红色 - 'r' 青色 - 'c' 品红 - 'm' 黄色 - 'y' 黑色 - 'k' 白色 - 'w' 线: 直线 - '-' 虚线 - '--' 点线 ...

  6. vs2013环境下boost配置

    编译boost库的过程这里暂时不写.  先写在vs2013下的boost配置. 新建一个工程, 1, 属性->C/C++,在附加包含目录添加或编辑Boost的文件路径, D:\boost_1_5 ...

  7. WPF 竖排文字(转)

    ---恢复内容开始--- 想做一个WPF 文字竖排 类似上图.用在TabItem的header上面. <TextBlock FontSize="30" Text=" ...

  8. 怎么安装Docker CE 17( Centos 7)

    Docker CE for Centos 7 yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manage ...

  9. bzoj 5120 [2017国家集训队测试]无限之环——网络流

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=5120 旋转的话相当于去掉一个插头.新增一个插头,所以在这两个插头之间连边并带上费用即可. 网 ...

  10. apache编译参数详解

    常用编译参数: ./configure     //配置源代码树–prefix=/usr/local/apache    //体系无关文件的顶级安装目录PREFIX ,也就Apache的安装目录.–e ...