@

缓存的重要性是不言而喻的。 使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。

mybatis 也提供了对缓存的支持, 分为一级缓存和二级缓存。 但是在默认的情况下, 只开启一级缓存(一级缓存是对同一个 SqlSession 而言的)。

以下的项目是在mybatis 初步使用(IDEA的Maven项目, 超详细)的基础上进行。

对以下的代码, 你也可以从我的GitHub中获取相应的项目。

1 一级缓存

同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)

也就是只有在参数和 SQL 完全一样的情况下, 才会有这种情况。

1.1 同一个 SqlSession

@Test
public void oneSqlSession() {
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 执行第一次查询
List<Student> students = studentMapper.selectAll();
for (int i = 0; i < students.size(); i++) {
System.out.println(students.get(i));
}
System.out.println("=============开始同一个 Sqlsession 的第二次查询============");
// 同一个 sqlSession 进行第二次查询
List<Student> stus = studentMapper.selectAll();
Assert.assertEquals(students, stus);
for (int i = 0; i < stus.size(); i++) {
System.out.println("stus:" + stus.get(i));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}

在以上的代码中, 进行了两次查询, 使用相同的 SqlSession, 结果如下

在日志和输出中:

第一次查询发送了 SQL 语句, 后返回了结果;

第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。

而且两次结果输入一致, 同时断言两个对象相同也通过。

1.2 不同的 SqlSession

 @Test
public void differSqlSession() {
SqlSession sqlSession = null;
SqlSession sqlSession2 = null;
try {
sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 执行第一次查询
List<Student> students = studentMapper.selectAll();
for (int i = 0; i < students.size(); i++) {
System.out.println(students.get(i));
}
System.out.println("=============开始不同 Sqlsession 的第二次查询============");
// 从新创建一个 sqlSession2 进行第二次查询
sqlSession2 = sqlSessionFactory.openSession();
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
List<Student> stus = studentMapper2.selectAll();
// 不相等
Assert.assertNotEquals(students, stus);
for (int i = 0; i < stus.size(); i++) {
System.out.println("stus:" + stus.get(i));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
if (sqlSession2 != null) {
sqlSession2.close();
}
}
}

在代码中, 分别使用 sqlSessionsqlSession2 进行了相同的查询。

其结果如下

从日志中可以看到两次查询都分别从数据库中取出了数据。 虽然结果相同, 但两个是不同的对象。

1.3 刷新缓存

刷新缓存是清空这个 SqlSession 的所有缓存, 不单单是某个键。

@Test
public void sameSqlSessionNoCache() {
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 执行第一次查询
Student student = studentMapper.selectByPrimaryKey(1);
System.out.println("=============开始同一个 Sqlsession 的第二次查询============");
// 同一个 sqlSession 进行第二次查询
Student stu = studentMapper.selectByPrimaryKey(1);
Assert.assertEquals(student, stu);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}

如果是以上, 没什么不同, 结果还是第二个不发 SQL 语句。

在此, 做一些修改, 在 StudentMapper.xml 中, 添加

flushCache="true"

修改后的配置文件如下:

<select id="selectByPrimaryKey" flushCache="true" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from student
where student_id=#{id, jdbcType=INTEGER}
</select>

结果如下:

第一次, 第二次都发送了 SQL 语句, 同时, 断言两个对象相同出错。

1.4 总结

  1. 在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;

  2. 不同的 SqlSession 之间的缓存是相互隔离的;

  3. 用一个 SqlSession, 可以通过配置使得在查询前清空缓存;

  4. 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

2 二级缓存

二级缓存存在于 SqlSessionFactory 生命周期中。

2.1 配置二级缓存

2.1.1 全局开关

在 mybatis 中, 二级缓存有全局开关和分开关, 全局开关, 在 mybatis-config.xml 中如下配置:

<settings>
<!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 -->
<setting name="cacheEnabled" value="true"/>
</settings>

默认是为 true, 即默认开启总开关。

2.1.2 分开关

分开关就是说在 *Mapper.xml 中开启或关闭二级缓存, 默认是不开启的。

2.1.3 entity 实现序列化接口

public class Student implements Serializable {

    private static final long serialVersionUID = -4852658907724408209L;

    ...

}

2.2 使用二级缓存

@Test
public void secendLevelCacheTest() { // 获取 SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取 Mapper 对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 使用 Mapper 接口的对应方法,查询 id=2 的对象
Student student = studentMapper.selectByPrimaryKey(2);
// 更新对象的名称
student.setName("奶茶");
// 再次使用相同的 SqlSession 查询id=2 的对象
Student student1 = studentMapper.selectByPrimaryKey(2);
Assert.assertEquals("奶茶", student1.getName());
// 同一个 SqlSession , 此时是一级缓存在作用, 两个对象相同
Assert.assertEquals(student, student1); sqlSession.close(); SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
Student student2 = studentMapper1.selectByPrimaryKey(2);
Student student3 = studentMapper1.selectByPrimaryKey(2);
// 由于我们配置的 readOnly="true", 因此后续同一个 SqlSession 的对象都不一样
Assert.assertEquals("奶茶", student2.getName());
Assert.assertNotEquals(student3, student2); sqlSession1.close();
}

结果如下:

2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 242282810.
2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:26,897 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==> Preparing: select student_id, name, phone, email, sex, locked, gmt_created, gmt_modified from student where student_id=?
2018-09-29 23:14:26,999 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==> Parameters: 2(Integer)
2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Columns: student_id, name, phone, email, sex, locked, gmt_created, gmt_modified
2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Row: 2, 小丽, 13821378271, xiaoli@mybatis.cn, 0, 0, 2018-09-04 18:27:42.0, 2018-09-04 18:27:42.0
2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Total: 1
2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.0
2018-09-29 23:14:27,108 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 242282810 to pool.
2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.3333333333333333
2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.5

以上结果, 分几个过程解释:

第一阶段:

  1. 在第一个 SqlSession 中, 查询出 student 对象, 此时发送了 SQL 语句;
  2. student更改了name 属性;
  3. SqlSession 再次查询出 student1 对象, 此时不发送 SQL 语句, 日志中打印了 「Cache Hit Ratio」, 代表二级缓存使用了, 但是没有命中。 因为一级缓存先作用了。
  4. 由于是一级缓存, 因此, 此时两个对象是相同的。
  5. 调用了 sqlSession.close(), 此时将数据序列化并保持到二级缓存中。

第二阶段:

  1. 新创建一个 sqlSession.close() 对象;
  2. 查询出 student2 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 3 个对象,但只有一个命中, 因此 命中率 1/3=0.333333;
  3. 查询出 student3 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 4 个对象,但只有一个命中, 因此 命中率 2/4=0.5;
  4. 由于 readOnly="true", 因此 student2student3 都是反序列化得到的, 为不同的实例。

2.3 配置详解

查看 dtd 文件, 可以看到如下约束:

<!ELEMENT cache (property*)>
<!ATTLIST cache
type CDATA #IMPLIED
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED
>

从中可以看出:

  1. cache 中可以出现任意多个 property子元素;
  2. cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking.

2.3.1 type

type 用于指定缓存的实现类型, 默认是PERPETUAL, 对应的是 mybatis 本身的缓存实现类 org.apache.ibatis.cache.impl.PerpetualCache

后续如果我们要实现自己的缓存或者使用第三方的缓存, 都需要更改此处。

2.3.2 eviction

eviction 对应的是回收策略, 默认为 LRU

  1. LRU: 最近最少使用, 移除最长时间不被使用的对象。

  2. FIFO: 先进先出, 按对象进入缓存的顺序来移除对象。

  3. SOFT: 软引用, 移除基于垃圾回收器状态和软引用规则的对象。

  4. WEAK: 弱引用, 移除基于垃圾回收器状态和弱引用规则的对象。

2.3.3 flushInterval

flushInterval 对应刷新间隔, 单位毫秒, 默认值不设置, 即没有刷新间隔, 缓存仅仅在刷新语句时刷新。

如果设定了之后, 到了对应时间会过期, 再次查询需要从数据库中取数据。

2.3.4 size

size 对应为引用的数量,即最多的缓存对象数据, 默认为 1024

2.3.5 readOnly

readOnly 为只读属性, 默认为 false

  1. false: 可读写, 在创建对象时, 会通过反序列化得到缓存对象的拷贝。 因此在速度上会相对慢一点, 但重在安全。

  2. true: 只读, 只读的缓存会给所有调用者返回缓存对象的相同实例。 因此性能很好, 但如果修改了对象, 有可能会导致程序出问题。

2.3.6 blocking

blocking 为阻塞, 默认值为 false。 当指定为 true 时将采用 BlockingCache 进行封装。

使用 BlockingCache 会在查询缓存时锁住对应的 Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据。

2.4 注意事项

  1. 由于在更新时会刷新缓存, 因此需要注意使用场合:查询频率很高, 更新频率很低时使用, 即经常使用 select, 相对较少使用delete, insert, update

  2. 缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。但刷新缓存是刷新整个 namespace 的缓存, 也就是你 update 了一个, 则整个缓存都刷新了。

  3. 最好在 「只有单表操作」 的表的 namespace 使用缓存, 而且对该表的操作都在这个 namespace 中。 否则可能会出现数据不一致的情况。

一起学 mybatis

你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!

我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!

mybatis最新源码和注释

mybatis 缓存的使用, 看这篇就够了的更多相关文章

  1. Vue学习看这篇就够

    Vue -渐进式JavaScript框架 介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的 ...

  2. ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  3. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...

  4. 想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了!

    想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了!   很多通信系统发展到某种程度都会有小型化的趋势.一方面小型化可以让系统更加轻便和有效,另一方面,日益发展的IC**技术可以用更低的成本生产 ...

  5. [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了

    [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 本文首发自:博客园 文章地址: https://www.cnblogs.com/yilezhu/p/ ...

  6. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  7. 【转】ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    原文链接:https://www.cnblogs.com/yilezhu/p/9241261.html 引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必 ...

  8. Pycharm新手教程,只需要看这篇就够了

    pycharm是一款高效的python IDE工具,它非常强大,且可以跨平台,是新手首选工具!下面我给第一次使用这款软件的朋友做一个简单的使用教程,希望能给你带来帮助! 目前pycharm一共有两个版 ...

  9. Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介

    Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...

  10. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

随机推荐

  1. springmvc拦截器验证登录时间

    在前一篇[Filter实现用户名验证]的随笔里,记录了如何使用filter 这次增加了拦截器实现 ①filter实现用户登陆时验证用户名是否为null ②interceptor实现用户登陆时时间判断, ...

  2. 【Linux命令与工具】磁盘与目录的容量——df和du

    df(disk free):列出文件系统的整体磁盘使用量 用法: df [-akmhi] [目录或文件名] 参数: -a: 列出所有的文件系统,包括系统特有的/proc等文件系统 -k: 以KB的容量 ...

  3. git 放弃本地修改 强制更新

    git reset --hard origin/master

  4. LeetCode:Text Justification

    题目链接 Given an array of words and a length L, format the text such that each line has exactly L chara ...

  5. 关于“学习Linux用什么系统”的解答

    首先,阐述一下,我个人的观点——这个问题我曾经也想了很久了,这绝不是长篇大论后就是简单一句,适合你的就是最好的.其实,很多人看到这一句,心里已经有成千上万个奔腾了(至少我当时是这样的),为什么?因为我 ...

  6. 将JDBC ResultSet结果集变成List

    private List<Map<String, Object>> list = new ArrayList<Map<String,Object>>() ...

  7. 如何用angularjs制作一个完整的表格之四__自定义ng-model标签的属性使其支持input之外的html元素

    有的时候我们需要为非input类型的元素添加ng-model来实现双向的数据绑定,从而减少冗余代码,那么可以尝试一下的方式 例如:我页面中使用了contenteditable这个属性来实现用户可直接编 ...

  8. 微软Ajax--UpdatePanel控件

    今天用做日历显示本月的考勤记录,用到了UpdatePanel控件,才发现对这个控件并不太了解,所以找了点儿资料,整理了一下给大家发上来! 一.UpdatePanel的结构 <asp:Script ...

  9. MySQL DATE_FORMAT

    MySQL   DATE_FORMAT(date,format) 根据format字符串格式化date值 (在format字符串中可用标志符: %M 月名字(January……December) %W ...

  10. CYQ.data 框架结构

    -------------------------V5.0开始(刚开始过滤版本:有些更新功能迁到V4,所以记录在V4那)-----------------------------7:Insert方法增 ...