MyBatis 入门教程之六
大纲
- MyBatis 入门教程之一
- MyBatis 入门教程之二
- MyBatis 入门教程之三
- MyBatis 入门教程之四
- MyBatis 入门教程之五
- MyBatis 入门教程之六
- MyBatis 入门教程之七
- MyBatis 入门教程之八
缓存机制
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。MyBatis 中默认定义了两级缓存:
- 一级缓存和二级缓存
- 默认情况下,只有一级缓存(
SqlSession
级别的缓存,也称为本地缓存)开启 - 二级缓存需要手动开启和配置,它是基于
namespace
级别的缓存 - 为了提高扩展性,MyBatis 定义了缓存接口
Cache
,可以通过实现Cache
接口来自定义二级缓存
- 默认情况下,只有一级缓存(
一级缓存
一级缓存介绍
- 一级缓存(local cache),即本地缓存,作用域默认为
SqlSession
。当Session
执行flush
或close
操作后,该Session
中的所有Cache
将被清空。 - 本地缓存默认是一直开启的(不能被关闭),但可以调用
sqlSession.clearCache()
来清空本地缓存,或者改变缓存的作用域。 - 在 MyBatis
3.1
之后,支持配置本地缓存的作用域,在mybatis.xml
中配置localCacheScope
属性即可。MyBatis 利用本地缓存可以防止循环引用和加速重复嵌套查询。localCacheScope
属性的可选值为SESSION | STATEMENT
,默认值为SESSION
,这种情况下会缓存一个会话中执行的所有查询。若设置值为STATEMENT
,本地会话仅用在语句执行上,对相同SqlSession
的不同调用将不会共享数据。 - 同一个会话期间,只要查询过的数据都会被保存在当前
SqlSession
的一个Map
中,它的key
由HashCode + 查询的 SqlId + 编写的 SQL 查询语句 + 参数
构成。
一级缓存使用案例
本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-16
。
1 | public class MyBatisApplication { |
上述代码执行之后,可以发现 MyBatis 只会发出一条 SQL 语句(如下所示),且两次查询到的对象的地址是相同的,即属于同一个对象,这是由于 MyBatis 一级缓存(默认开启)起的作用。
1 | 22:36:12,278 DEBUG getEmpById:137 - ==> Preparing: select id, last_name as lastName, gender, email from t_employee where id = ? |
一级缓存失效的场景
一级缓存失效的四种场景:
- 使用不同的
SqlSession
进行查询 - 同一个
SqlSession
,但是查询条件不同 - 同一个
SqlSession
,但是在两次查询期间手动清空了一级缓存 - 同一个
SqlSession
,但是在两次查询期间执行了任何一次增删改操作
二级缓存
二级缓存介绍
- 二级缓存默认不开启,需要手动配置
- 二级缓存在
SqlSession
提交或关闭之后才会生效 - MyBatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现
Serializable
接口 - 当
SqlSession
提交或者关闭后,一级缓存中的数据会被保存到二级缓存中,下次使用新的会话进行查询时,就可以使用到二级缓存的数据 - 二级缓存(second level cache)是全局作用域缓存,并且是基于
namespace
级别的缓存,一个namespace
对应一个二级缓存;不同namespace
查询出的数据会放在自己对应的缓存(Map)中
二级缓存启用步骤
- 第一步、在 MyBatis 的全局配置文件中开启二级缓存
<setting name= "cacheEnabled" value="true"/>
- 第二步、在需要使用二级缓存的 SQL 映射文件里添加
<cache>
标签来配置缓存 - 第三步、POJO(Plain Ordinary Java Object - 简单的 Java 对象)需要实现
Serializable
接口
二级缓存相关属性
在 MyBatis 的 SQL 映射文件中,<cache>
标签拥有以下属性:
二级缓存相关设置
全局配置中的 cacheEnable 标签
:启用二级缓存的开关,二级缓存默认是关闭的,一级缓存是一直是开启的select 标签的 useCache 属性
:配置当前的select
语句是否使用二级缓存,一级缓存一直是使用的SQL 标签的 flushCache 属性
:增删改操作默认是flushCache=true
,即 SQL 执行以后会同时清空一级和二级缓存,而查询操作默认是flushCache=false
sqlSession.clearCache()
:只是用来清除一级缓存全局配置中的 localCacheScope 属性
:本地缓存作用域(SESSION | STATEMENT),设置为SESSION
时,当前会话的所有数据会被缓存到会话里;设置值为STATEMENT
时,本地会话仅用在语句执行上,对相同SqlSession
的不同调用将不会共享数据
值得一提的是,当在某一个作用域(一级缓存 / 二级缓存) 进行了增删改操作后,默认该作用域下的所有缓存数据将被清空。
二级缓存使用案例
本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-17
。
特别注意
- 1、二级缓存在
SqlSession
提交或关闭之后才会生效 - 2、当
SqlSession
提交或者关闭后,一级缓存中的数据会被保存到二级缓存中,下次使用新的会话进行查询时,就可以使用到二级缓存的数据
- 全局配置文件
1 | <configuration> |
- SQL 映射文件
1 | <mapper namespace="com.clay.mybatis.dao.EmployeeMapper"> |
- Java 代码
1 | public class MyBatisApplication { |
执行上述的代码,可以发现 MyBatis 只会发出一条 SQL 语句(如下所示),这说明二级缓存起了作用。
1 | 22:55:46,112 DEBUG getEmpById:137 - ==> Preparing: select id, last_name as lastName, gender, email from t_employee where id = ? |
一级与二级缓存原理图解
整合 Ehcache 第三方缓存
EhCache 是一个纯 Java 实现的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider
。
Ehcache 第三方缓存整合步骤
- 1、引入核心的依赖包,包括 Ehcache 包、MyBatis 整合包、日志包
1 | <dependency> |
- 2、在项目的
src/main/resources
目录下创建ehcache.xml
配置文件
1 |
|
标签 | 说明 |
---|---|
diskStore | 指定缓存数据在磁盘中的存储位置 |
defaultCache | 当借助 CacheManager.add("demoCache") 创建 Cache 时,EhCache 便会采用 <defalutCache> 标签指定的的缓存管理策略 |
属性 | 必填 | 说明 |
---|---|---|
maxElementsInMemory | 是 | 在内存中缓存的 Element 的最大数量 |
maxElementsOnDisk | 是 | 在磁盘上缓存的 Element 的最大数量,若是 0 则表示无穷大 |
eternal | 是 | 设定缓存的 Elements 是否永远不过期。如果为 true ,则缓存的数据始终有效,如果为 false ,则需要根据 timeToIdleSeconds 与 timeToLiveSeconds 进行判断 |
overflowToDisk | 是 | 设定当内存缓存溢出的时候,是否将过期的 Element 缓存到磁盘上 |
timeToIdleSeconds | 否 | 当缓存在 EhCache 中的数据前后两次访问的时间超过 timeToIdleSeconds 的属性取值时,这些数据便会删除,默认值是 0 ,表示缓存数据的可闲置时间无穷大 |
timeToLiveSeconds | 否 | 缓存 Element 的有效生命期,默认是 0 ,表示 Element 的存活时间无穷大 |
diskSpoolBufferSizeMB | 否 | 这个参数设置 DiskStore(磁盘缓存) 的缓存区大小。默认是 30MB ,每个 Cache 都应该有自己的一个缓冲区 |
diskPersistent | 否 | 在 VM 重启的时候是否启用磁盘保存 EhCache 中的数据,默认是 false |
diskExpiryThreadIntervalSeconds | 否 | 磁盘缓存的清理线程运行间隔,默认是 120 秒。每隔 120 秒,相应的线程会进行一次 EhCache 中数据的清理工作 |
memoryStoreEvictionPolicy | 否 | 当内存缓存达到最大,有新的 Element 加入的时候,移除缓存中 Element 的策略。默认是 LRU(最近最少使用) ,可选值还有 LFU(最不常使用) 和 FIFO(先进先出) |
- 3、配置 MyBatis 全局配置文件,开启二级缓存
1 | <configuration> |
- 4、配置 SQL 映射文件,添加
<cache>
标签
1 | <mapper namespace="com.clay.mybatis.dao.EmployeeMapper"> |
提示
- 二级缓存是全局作用域缓存,并且是基于
namespace
级别的缓存,一个namespace
对应一个二级缓存,不同namespace
查询出的数据会放在自己对应的缓存(Map)中 - 若想在命名空间(
namespace
)中共享相同的缓存配置和实例,则可以在 SQL 映射文件里使用<cache-ref>
标签来引用另外一个缓存
假设在 DepartmentMapper.xml
映射文件中,想引用 com.clay.mybatis.dao.EmployeeMapper
命名空间的二级缓存,可以使用 <cache-ref>
标签来实现:
1 | <mapper namespace="com.clay.mybatis.dao.DepartmentMapper"> |
- 5、验证整合结果
运行项目后,若输出的日志信息里有下面类似的内容,则说明 MyBatis 成功整合了 EhCache,或者可以查看 <diskStore>
标签指定的存储位置下有没有生成数据文件来验证整合结果。
1 | 23:35:03,108 DEBUG Segment:425 - put added 0 on heap |
使用第三方缓存的查询流程图
当执行一条查询 SQL 时,首先从二级缓存中进行查询,然后进入一级缓存中查询,最后执行 JDBC 查询。