Mybatis之缓存机制
一 概述
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存
- 一级缓存和二级缓存
(1) 默认情况下,只有一级缓存(SqlSession级别的缓存, 也称为本地缓存)开启。
(2)二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
(3)为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
二 一级缓存
- 一级缓存介绍
(1)一级缓存(local cache), 即本地缓存, 作用域默认 为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。
(2)本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域。
(3)在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置
localCacheScope:SESSION | STATEMENT,默认值是SESSION
说明:Mybatis利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询,默认值为Session,这种情况下会缓存一个会话中执行的所有查询。若是STATEMENT,本地会话仅用在语句执行上,对相同SqlSession的不同调用将不会共享数据。 - 一级缓存演示&失效情况
(1)同一次会话期间只要查询过的数据都会保存在当 前SqlSession的一个Map中
key:hashCode+查询的SqlId+编写的sql查询语句+参数
(2)一级缓存失效的四种情况
① 不同的SqlSession对应不同的一级缓存
② 同一个SqlSession但是查询条件不同
③ 同一个SqlSession两次查询期间执行了任何一次增删改操作
④ 同一个SqlSession两次查询期间手动清空了缓存
三 二级缓存
-
概述
(1)二级缓存:全局作用域缓存
(2)二级缓存默认不开启,需要手动配置
(3)MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口
(4)二级缓存在 SqlSession 关闭或提交之后才会生效 -
使用步骤
(1)在全局配置文件中开启二级缓存
<setting name= "cacheEnabled" value="true"/>
(2)在需要使用二级缓存的映射文件处使用cache配置缓存 **<cache />**
(3)**POJO需要实现Serializable接口**
四 缓存属性和设置
- 缓存相关属性
(1)eviction=“FIFO”:缓存回收策略
① LRU – 最近最少使用的:移除最长时间不被使用的对象。
② FIFO – 先进先出:按对象进入缓存的顺序来移除它们
③ SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
④ WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
⑤ 默认的是 LRU
(2)flushInterval:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
(3) size:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
(4)readOnly:只读,true,false
① true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象 不能被修改。这提供了很重要的性能优势。
② false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些, 但是安全,因此默认是false - 缓存相关设置
(1)全局setting的cacheEnable
配置二级缓存的开关。一级缓存一直是打开的
(2)select标签的useCache属性
配置这个select是否使用二级缓存。一级缓存一直是使用的
(3)sql标签的flushCache属性
增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。 查询默认flushCache=false
(4)sqlSession.clearCache()
只是用来清除一级缓存
(5)当在某一个作用域 (一级缓存Session/二级缓存 Namespaces) 进行了 C/U/D 操作后,默认该作用域下所 有 select 中的缓存将被clear
五 整合第三方缓存
- EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider
- MyBatis定义了Cache接口方便我们进行自定义扩展。
步骤:
(1)导入ehcache包,以及整合包,日志包ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
(2)编写ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--持久化磁盘路径-->
<diskStore path="D:\ehcache"/>
<!--默认缓存设置-->
<defaultCache maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="true"
maxElementsOnDisk="10000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="FIFO"
/>
<!--
<cache name 缓存名唯一标识
maxElementsInMemory="1000" 内存中最大缓存对象数
eternal="false" 是否永久缓存
timeToIdleSeconds="3600" 缓存清除时间 默认是0 即永不过期
timeToLiveSeconds="0" 缓存存活时间 默认是0 即永不过期
overflowToDisk="true" 缓存对象达到最大数后,将其写入硬盘
maxElementsOnDisk="10000" 磁盘最大缓存数
diskPersistent="false" 磁盘持久化
diskExpiryThreadIntervalSeconds="120" 磁盘缓存的清理线程运行间隔
memoryStoreEvictionPolicy="FIFO" 缓存清空策略
FIFO 先进先出
LFU less frequently used 最少使用
LRU least recently used 最近最少使用
/>
-->
<cache name="testCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true"
memoryStoreEvictionPolicy="FIFO">
</cache>
</ehcache>
(3)在Mapper文件中配置cache标签
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName); -->
<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
</mapper>
(4)测试类
@Test
public void testSecondLevelCache() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
//关闭二级缓存才生效
openSession.close();
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
Employee emp02 = mapper2.getEmpById(1);
System.out.println(emp02);
openSession2.close();
}finally{
}
}
参照缓存:若想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref元素来引用另外一个缓存
-
缓存流程图
解读:(1)客户从数据库获取数据视为一次会话,抽象为sqlSession对象
(2)一个Excutor包含增删改查的操作;
(3)CachingExcutor是对Excutor的包装,此处相当于代理模式
(4)当有会话时,先访问CachingExcutor对象,CachingExcutor先从二级缓存查找数据,如果有就直接返回;如果没有,就进入Excutor的一级缓存,如果还是没有就执行Excutor的增删改查返回结果,并将结果保存至缓存中,同一个sqlSession再次访问就可以从一级缓存中取了;
(5)由于mybatis的缓存只用了map实现,所以mybatis允许缓存由第三方缓存来实现,并定义了cache接口,第三方只要实现该接口即可,和mybatis整合在一起后由mybatis在程序中进行调用;