mybatis缓存机制

简介

MyBatis 包含一个非常强大的查询缓存特性,
它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存。

一级例子

= openSession.getMapper(EmpMapper.class);
Emp empById = mapper.getEmpById(3);
System.out.println(empById);
Emp empById2 = mapper.getEmpById(3);
System.out.println(empById==empById2);
结果是:
只发了一条sql语句,两个emp的内存地址是一样的
DEBUG 05-19 11:55:59,176 ==> Preparing: select eid id,ename name,email,gender from emp where eid= ? (BaseJdbcLogger.java:145)
DEBUG 05-19 11:55:59,199 ==> Parameters: 3(Integer) (BaseJdbcLogger.java:145)
DEBUG 05-19 11:55:59,220 <== Total: 1 (BaseJdbcLogger.java:145)
Emp [eid=3, ename=, email=jane.com, gender=, dept=null]
true

一级,二级缓存机制

/*
* mybatis的缓存机制
* 分为两级缓存:
* 一级缓存:(本地缓存):sqlSession级别的缓存,sqlsession级别的一个Map
* 一级缓存是一直开启的,我们关闭不了
* 与数据库同一次会话期间查询到的数据会放在本地缓存中,
* 以后如果需要获取相同的数据,直接从缓存中拿,
* 没有必要再去查询数据库
*
* 一级缓存失效的情况:
* 就是没有使用到当前一级缓存的情况,效果就是查询相同的数据还需要向数据库发出sql语句
* 1.sqlsession不同了
* 2.sqlsession相同,查询条件不同了,因为当前一级缓存里面没有这个数据
* 3.sqlsession相同,两次查询之间执行了增删改操作,因为这个增删改的操作可能对当前的数据有影响
* 4.sqlsession相同,手动清除了一级缓存,调用了openSession.clearCache();
*
* 二级缓存:(全局缓存)
* 这是基于namespace级别的缓存,一个namespace就对应一个二级缓存
* 机制原理:
* 1.一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
* 2.如果会话关闭,一级缓存中的数据就会被保存到二级缓存中
* 新的会话查询数据,就可以参照二级缓存的内容
* 3.sqlsession会话在EmpMapper中查询了Emp信息
* 在DeptMapper中查询了Dept信息
* 这两个不同的缓存信息是不能互相使用的
* 不同的namespace查出的数据会放在自己对应的缓存中(就是map)
* 效果:数据会从二级缓存中获取
* 注意:查出的数据会默认先放在一级缓存中的,
* 只有会话提交或者关闭后,一级缓存中的数据才会转移到二级缓存中
* 所以测试的时候,两个不同的会话调用同一个数据想使用缓存
* 必须在第二个会话调用前关闭第一个会话
* 如何使用:
* 1.开启全局二级缓存配置,其实默认是开启的,
* 但是我们默认是将自己需要的设置都写上
* <setting name="cacheEnabled" value="true"/>
* 2.哪一个映射文件需要使用二级缓存
* 在映射文件加上<cache></cache>标签
* 3.因为缓存使用到序列化技术,每个POJO都要实现序列化接口
*
* 使用缓存的一些小设置:
* 1.cacheEnabled=true
* false:关闭二级缓存,一级缓存是一直可以使用的
* 2.每一个select标签都有useCache="true";的设置
* false:就是不使用二级缓存,一级缓存还是可以使用的
* 3.每一个增删改标签都有默认的flushCache="true":(一级缓存,二级缓存都会被清除)
* 这个属性让增删改执行完成后清除缓存
* flushCache="true":一级缓存,二级缓存都会被清除)
* 而查询标签默认的flushCache="false",
* 而查询标签将flushCache="true",那么每次查询后就会清空缓存,缓存时没有被使用的
* 4.手动调用的sqlSession.clearCache(),只是清除当前的一级缓存,二级不会清除
* 5.全局设置还有这样一个参数:localCacheScope:本地缓存的作用域
* 有两个取值:
* SESSION:一级缓存,当前会话的所有数据保存在会话缓存中;
* STATEMENT:没有一级缓存了,相当于禁用了一级缓存
*/

@org.junit.Test
public void test9() throws IOException
{
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try
{
SqlSession openSession1 = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
EmpMapper mapper1 = openSession1.getMapper(EmpMapper.class);
EmpMapper mapper2 = openSession2.getMapper(EmpMapper.class);

Emp empById = mapper1.getEmpById(7);
System.out.println(empById);

openSession1.close();
Emp empById2 = mapper2.getEmpById(7);
System.out.println(empById2);
openSession2.close();

}finally
{
}
}
<cache eviction="FIFO" flushInterval="10000" readOnly="false" size="1024" ></cache>
<!--
eviction:缓存回收策略:
LRU 最近最少使用的:移除最长时间不被使用的对象。
FIFO 先进先出:按对象进入缓存的顺序来移除它们。
SOFT 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读,取值是布尔值
true:只读:mybatis认为所有从缓存中获取数据的操作都是只读的,不会修改数据
mybatis为了加快获取速度,直接将数据在缓存中的引用交给用户
这样速度是快了,但是这样不安全
false:非只读,mybatis觉得获取的数据可能被修改
所以就先会利用序列化和反序列化的技术克隆一份新的数据给你
这样安全,但是速度慢
size:缓存存放多少个元素
type:指定自定义的全类名,自定义的类需要实现Cache接口
-->

POJO

public class Emp implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer eid;
private String ename;
private String email;
private String gender;
private Dept dept;
结果:
可以发现,第二次拿到的结果不是发sql得到的,而是
DEBUG 05-20 12:51:52,960 Cache Hit Ratio [bean.EmpMapper]: 0.5 (LoggingCache.java:62)
直接从二级缓存里面获取,获取了两次,第二次获取成功,所以后面的数字是1/2=0.5

DEBUG 05-20 12:51:52,791 Cache Hit Ratio [bean.EmpMapper]: 0.0 (LoggingCache.java:62)
DEBUG 05-20 12:51:52,805 ==> Preparing: select eid id,ename name,email,gender from emp where eid= ? (BaseJdbcLogger.java:145)
DEBUG 05-20 12:51:52,871 ==> Parameters: 7(Integer) (BaseJdbcLogger.java:145)
DEBUG 05-20 12:51:52,940 <== Total: 1 (BaseJdbcLogger.java:145)
Emp [eid=7, ename=jane, email=jane.com, gender=, dept=null]
DEBUG 05-20 12:51:52,960 Cache Hit Ratio [bean.EmpMapper]: 0.5 (LoggingCache.java:62)
Emp [eid=7, ename=jane, email=jane.com, gender=, dept=null]

缓存原理

原理示意图

MyBatis框架:缓存机制,mybatis整合ehcache_数据

实现

MyBatis框架:缓存机制,mybatis整合ehcache_一级缓存_02

执行顺序
缓存的顺序:二级缓存----->一级缓存------>数据库

mybatis整合ehcache

首先需要导入三个包
ehcache需要的jar包
​​​ehcache-core-2.6.8.jar​​​ ehcache允许需要的日志包
​slf4j-api-1.6.1.jar​​​​slf4j-log4j12-1.6.2.jar​​ 有着三个包后我们就可以写自己的实现类,实现Cache接口就行
但是自己写实现类好麻烦呀,它是一个成熟的项目,应该自己写实现的
所以mybatis就帮你写好了,官网的git那里有,下载好后导入相应的jar包
​mybatis-ehcache-1.0.3.jar​​ 根据官方文档说明,它需要在映射文件配置好,还有需要一个ehcache.xml文件就可以使用ehcache了
EmpMapper映射文件配置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

ehcache.xml文件
这个不是我们的关注点,随便了解一下就行了

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="E:\new Java\STSworkspace\TempData\ehcache" />

<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

<!--
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目,数字就是内存中数据的条数
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->

这样就可以使用ehcache的缓存了
也可以使用引用缓存
namespace就是你要引用哪个映射文件的命名空间的缓存机制

<cache-ref namespace="bean.EmpMapper"/>