提示:这是MyBatis的第五篇,有兴趣的话可以看下:
一: MyBatis复习笔记整理
二: MyBatis的resultMap标签 自定义封装返回值类型
三: MyBatis动态SQL官方文档
四: MyBatis动态SQL学习笔记
将查询到的结果缓存到本地,再次查询时,从本地查询,无需再次发送SQL查询请求数据库。
MyBatis
有两种缓存:
一级缓存(本地缓存):
sqlSession
会话级别的缓存。一级缓存是一直开启的;SqlSesison
级别的一个Map
。
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,会直接从缓存中拿,没必要再去查询数据库。
@Test
public void testFirstLevelCache() {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById("1");
// openSession.clearCache();
Employee employee2 = mapper.getEmpById("1");
System.out.println(employee);
System.out.println(employee2);
System.out.println(employee == employee2);
} finally {
openSession.close();
}
}
输出:
2021-07-12 20:10:24,480 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - ==> Preparing: select * from tbl_Employee where id = ?
2021-07-12 20:10:24,540 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - ==> Parameters: 1(String)
2021-07-12 20:10:24,570 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - <== Total: 1
Employee [id=1, lastName=tom, email=[email protected], gender=1]
Employee [id=1, lastName=tom, email=[email protected], gender=1]
true
两次查询,只发送了一次SQL查询请求,并且2个Employee对象相等。
一级缓存失效情况:
sqlSession
不同。(一级缓存是会话级别的)sqlSession
相同,查询条件不同。(当前一级缓存中还没有这个数据)sqlSession
相同,两次查询之间执行了增删改操作。(这次操作可能对当前数据有影响)sqlSession
相同,手动清除了一级缓存。(openSession.clearCache()
清空缓存)二级缓存(全局缓存):
基于namespace
级别的缓存:一个namespace
对应一个二级缓存:
工作机制:
sqlSession
== 》EmployeeMapper
== 》Employee
DepartmentMapper
== 》Department
namespace
查出的数据会放在自己对应的缓存中。(map
)效果:数据会从二级缓存中获取。
查出的数据会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
二级缓存的使用方法:
<setting name="cacheEnabled" value="true"/>
<cache><cache>
public class Employee implements Serializable {
cache
标签的相关属性设置:
<cache eviction="" flushInterval="" readOnly="" size="" type="">cache>
1、eviction
缓存驱逐策略:
LRU
— 最近最少使用的:移除最长时间不被使用的对象。FIFO
— 先进先出:按对象进入缓存的顺序来移除它们。SOFT
— 软引用:移除基于垃圾回收站状态和软引用规则的对象。WEAK
— 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。LRU
。移除最近最少使用的缓存对象。2、flushInterval
缓存刷新间隔:
3、readOnly
是否只读:
true
:只读:mybatis
认为所有从缓存中获取数据的都是只读操作,不会修改数据。mybatis
为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快。false
:非只读:mybatis
觉得获取的数据可能会被修改。mybatis
会利用序列化&反序列化的技术克隆一份新的数据给你。安全,但速度慢。false
非只读4、size
:缓存存放多少元素
5、type
:指定自定义缓存的全类名。
mybatis
的Cache
接口后,填写全类名Java代码?拿来吧你:
@Test
public void testSecondLevelCache() {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
// 获取2个sqlSession对象测试二级缓存
SqlSession openSession2 = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById("1");
// 一定要关闭sqlSession会话,才能将会话中的一级缓存转移到二级缓存中
openSession.close();
Employee employee2 = mapper2.getEmpById("1");
System.out.println(employee);
System.out.println(employee2);
System.out.println(employee == employee2);
} finally {
openSession2.close();
}
}
输出结果,来拿:
2021-07-12 21:12:03,995 [main] DEBUG [com.atguigu.dao.EmployeeMapper] - Cache Hit Ratio [com.atguigu.dao.EmployeeMapper]: 0.0
2021-07-12 21:12:04,012 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - = => Preparing: select * from tbl_Employee where id = ?
2021-07-12 21:12:04,074 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - = => Parameters: 1(String)
2021-07-12 21:12:04,103 [main] DEBUG [com.atguigu.dao.EmployeeMapper.getEmpById] - <= = Total: 1
2021-07-12 21:12:04,121 [main] DEBUG [com.atguigu.dao.EmployeeMapper] - Cache Hit Ratio [com.atguigu.dao.EmployeeMapper]: 0.5
Employee [id=1, lastName=tom, [email protected], gender=1]
Employee [id=1, lastName=tom, [email protected], gender=1]
false
Cache Hit Ratio
表示缓存命中率:第一次是0.0
,因为没有缓存。第二次成功命中,2
次命中一次,所以第二次为0.5
,也就是50%
。
可以看到2个不同的sqlSession
会话进行的2次查询,只发送了一个SQL语句的查询请求,说明二级缓存成功生效。
这里打印false
是因为readOnly
为默认值false
,也就是mybatis
认为可能数据会被修改,于是将数据序列化
copy了一份。
1、cacheEnabled="true"
: "false"
: 关闭(二级)缓存(一级缓存不受影响)
2、每个select
标签都有useCache="true"
:
false
:不使用缓存(一级缓存依然使用,二级缓存不使用)3、每个增删改标签的:flushCache="true"
:(一级二级都会清除)
flushCache="true"
:一级缓存清空,二级缓存也清空select
查询标签默认flushCache="false"
:flushCache="true"
,那么每次查询之后都会清空缓存,缓存是没法用的。4、sqlSession.clearCache();
只是清除当前session
的一级缓存;
5、localCacheScope
:本地缓存作用域:
原理:MyBatis
原生缓存底层就是一个Map
实现的,比较简单。所以官方预留了Cache
接口,只需要实现Cache
接口,在配置中使用,即可提供给MyBatis
调用,实现自定义缓存。
第三方缓存整合:
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcache-coreartifactId>
<version>2.6.8version>
dependency>
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.14version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.6.1version>
dependency>
2、在mapper.xml
中使用自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache">cache>
在cache
标签中的type
属性指定cache
接口的实现类全类名即可。
3、把ehcache.xml
配置文件放到类路径
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="E:\work\demo\Tmp_EhCache"/>
<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
ehcache>
4、其他mapper.xml
引用
无需重复以上配置,只需要使用cache_ref
标签的namespace
属性指定上面已配置的命名空间即可。
<cache-ref namespace="com.atguigu.dao.EmployeeMapper"/>