MyBatis的查询缓存机制,根据缓存区的作用域(生命周期)可以划分为2种:一级查询缓存和二级查询缓存
1、一级查询缓存
MyBatis一级查询缓存是基于org.apache.ibatis.catch.impl.PerpetualCache类的HsahMap本地缓存,其作用域是SqlSession。
当一个SqlSessoin结束后,该SqlSession中的一级查询缓存也就不存在了。
MyBatis默认一级查询缓存是开启状态,且不能关闭。
一个SqlSession操作一个mapper.xml文件(sqlSession.getMapper(IStudentDao.class)),也就是说一个SqlSession对应一个namespace。但是一个namespace可以对多个SqlSession
1)证明SqlSession一级缓存是存在的
@Test
public void test01() {
Student student = dao.selectById(2);
System.out.println(student);
Student student2 = dao.selectById(2);
System.out.println(student2);
}
2)测试输出:
[DEBUG] ==> Preparing: select id,name,age,score from student where id = ?
[DEBUG] ==> Parameters: 2(Integer)
[TRACE] <== Columns: id, name, age, score
[TRACE] <== Row: 2, 李四, 24, 94.5
[DEBUG] <== Total: 1
Student [id=2, name=李四, age=24, score=94.5]
Student [id=2, name=李四, age=24, score=94.5]
第二次
查询没输出
sql语句,说明
没有查询
数据库,直接从
SqlSession的缓存中
查询数据
SqlSession缓存的底层实现是一个Map,Map的value是查询结果
Map的key,即查询依据,不同的ORM框架,查询的依据是不同的
MyBatis的查询依据是:SQL的id + SQL语句
Hibernate的查询依据是:查询结果对象的id
3)增删改操作,无论是否提交,都会刷新一级缓存,使查询再次从DB中select
@Test
public void test03() {
Student student = dao.selectById(2);
System.out.println(student);
// 增删改操作都会刷新SqlSession一级缓存【刷新缓存即清空缓存】,肯定要刷新缓存,不然从缓存读取可能是脏数据
dao.insertStudent(new Student("哈哈", 91, 100));
// sqlSession.commit(); //不提交不会写入数据库【不提交也同样会刷新缓存】
Student student2 = dao.selectById(2);
System.out.println(student2);
}
4)测试输出
[DEBUG] ==> Preparing: select id,name,age,score from student where id = ?
[DEBUG] ==> Parameters: 2(Integer)
[TRACE] <== Columns: id, name, age, score
[TRACE] <== Row: 2, 李四, 24, 94.5
[DEBUG] <== Total: 1
Student [id=2, name=李四, age=24, score=94.5]
[DEBUG] ==> Preparing: insert into student(name,age,score) values(?,?,?)
[DEBUG] ==> Parameters: 哈哈(String), 91(Integer), 100.0(Double)
[DEBUG] <== Updates: 1
[DEBUG] ==> Preparing: select id,name,age,score from student where id = ?
[DEBUG] ==> Parameters: 2(Integer)
[TRACE] <== Columns: id, name, age, score
[TRACE] <== Row: 2, 李四, 24, 94.5
[DEBUG] <== Total: 1
Student [id=2, name=李四, age=24, score=94.5]
2、二级查询缓存
1)内置二级缓存
MyBatis查询缓存的作用域是根据映射文件mapper的namespace划分的,相同namespace的mapper查询数据存放在同一个缓存区域,不同namespace下的数据互不干扰。
无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的。
不同之处在于,SqlSession一旦关闭,则SqlSession中缓存的数据将不存在,即一级缓存就不存在。而二级缓存的生命周期会与整个应用同步。
开启内置二级缓存步骤:
a)Student实体类实现序列化接口
public class Student implements Serializable {
…………
}
b)在mapper.xml映射文件中添加<cache />标签
<!-- 开启mybatis自带二级缓存 ,属性默认就行-->
<cache />
<cache />标签同样有属性,空代表使用默认属性,默认内置二级缓存。
eviction:逐出策略。
FIFO:先进先出
LRU: 未被使用时间最长的
flushInterval:刷新缓存的时间间隔,单位毫秒。刷新即清空缓存。一般不指定,即当执行增删改操作时刷新缓存
readOnly:设置缓存中的对象是否只读
size:缓存中对象最多可以存放的个数
c)证明内置二级缓存是存在的
@Test
public void test01() {
sqlSession = MyBatisUtils.getSqlSession();
dao = sqlSession.getMapper(IStudentDao.class);
Student student = dao.selectById(2);
System.out.println(student);
sqlSession.close();
sqlSession = MyBatisUtils.getSqlSession();
dao = sqlSession.getMapper(IStudentDao.class);
Student student2 = dao.selectById(2);
System.out.println(student2);
}
sqlSession.close(); 关闭之后,SqlSession一级缓存也关闭了。
所以不会从一级缓存查询,不开启二级缓存的话,会去数据库查
d)测试输出:
[DEBUG] Cache Hit Ratio [com.xinm.mybatis.dao.IStudentDao]: 0.0
[DEBUG] ==> Preparing: select id,name,age,score from student where id = ?
[DEBUG] ==> Parameters: 2(Integer)
[TRACE] <== Columns: id, name, age, score
[TRACE] <== Row: 2, 李四, 24, 94.5
[DEBUG] <== Total: 1
Student [id=2, name=李四, age=24, score=94.5]
[DEBUG] Cache Hit Ratio [com.xinm.mybatis.dao.IStudentDao]: 0.5
Student [id=2, name=李四, age=24, score=94.5]
CaChe Hit Ratio
缓存命中率,可以看出
二级缓存已经开启。
第一次命中率 0.0(没命中) ,第二次命中率0.5(命中) ,且没有执行Sql去数据库中查询。说明从缓存中查询
当然可以单独设置sql查询不使用缓存,那么就不会缓存数据,肯定也不会去缓存中查询数据。
当然这里说的缓存是二级缓存。一级缓存是MyBatis默认开启且不能关闭,也不能设置的。
<!-- 设置不使用缓存,默认为true -->
<select id="selectById2" resultType="Student" useCache="false">
select id,name,age,score from student where id = #{xxx}
</select>
e)增删改操作,同样会刷新二级缓存
1)对于二级缓存的清空
不提交的情况: 不刷新二级缓存【只刷新了 value=null】提交的情况: 刷新二级缓存【 key,value都清空,即删除 Entry对象】
2)对于一级缓存的清空
都会刷新一级缓存,且 不能设置不刷新当然也可以单独设置sql增删改操作不刷新缓存,当然这里说的缓存同样是二级缓存。
<!-- 不刷新二级缓存,默认为true刷新 -->
<!-- SqlSession一级缓存,不可设置,增删改都会刷新一级缓存 -->
<insert id="insertStudent2" flushCache="false">
insert into student(name,age,score) values(#{name},#{age},#{score})
</insert>
f)二级缓存的关闭
1)全局关闭
<settings>
<!-- 二级缓存 总开关,不写默认代表开启 -->
<setting name="cacheEnabled" value="false"/>
</settings>
2)局部关闭
<!-- 设置不使用缓存,默认为true -->
<select id="selectById2" resultType="Student" useCache="false">
select id,name,age,score from student where id = #{xxx}
</select>
g)二级缓存使用原则
1)只能在同一个命名空间(namespace)下使用
2)在单表上使用
3)查询远远多于修改时使用
h)总结:
一级缓存没有配置文件可以设置,因为MyBatis默认SqlSession一级缓存开启,且不能关闭,不能设置。
二级缓存其实很少用,因为很多表都存在关联关系,必有关联查询。一般只对表查询【很】多更新【极】少的情况
2、ehCache二级查询缓存
MyBatis的 特长是 SQL操作, 缓存数据管理 不是其特长,为了 提高缓存的性能,MyBatis 允许使用 第三方缓存产品。 ehCache就是一种a)使用ehCache二级缓存,实体类无需实现序列化接口。
b)用到2个jar包,一个是ehCache核心jar包,一个是MyBatis与ehCache整合的插件jar包
c)添加ehcache.xml配置文件
ehCache核心jar包下面有一个ehcache-failsafe.xml。拷贝重命名为ehcache.xml,放在类路径下
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 缓存在磁盘的路径 -->
<!-- java.io.tmpdir 本地路径为:C:\Users\UserName\AppData\Local\Temp\ -->
<diskStore path="java.io.tmpdir"/>
<!-- <diskStore path="F:/ehcache"/> -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!--
maxElementsInMemory:指定内存缓存区可以存放的最大缓存对象个数
eternal:设置缓存对象是否会过期。true表示永久不会过期,此时忽略timeToIdleSeconds和timeToLiveSeconds属性。默认为false
timeToIdleSeconds:允许对象处于空闲状态的最长时间,单位为秒。设置为0,对象可以无限期的处于空闲状态
timeToLiveSeconds:允许对象存在于缓存中的最长时间,单位为秒。设置为0,对象可以无限期的存在于缓存中
注意只有timeToLiveSeconds >= timeToIdleSeconds 才有意义
maxElementsOnDisk:指定硬盘缓存区可以存放的最大缓存对象个数
diskExpiryThreadIntervalSeconds:指定硬盘中缓存对象的失效时间间隔
memoryStoreEvictionPolicy:如果内存缓存区超过限制,选择移向硬盘缓存区中的对象时,使用的策略。
支持三种策略:
FIFO:First In First Out 先进先出
LFU:Less Frequently Used 最少使用
LRU:Less Recently Used 最近最少使用
persistence:表示Cache的持久化,它只有一个属性strategy,表示当前Cache对应的持久化策略。其可选值如下:
localTempSwap:当堆内存或者非堆内存里面的元素已经满了的时候,将其中的元素临时的存放在磁盘上,一旦重启就会消失。 localRestartable:该策略只对企业版Ehcache有用。它可以在重启的时候将堆内存或者非堆内存里面的元素持久化到硬盘上,重启之后再从硬盘上恢复元素到内存中。
none:不持久化缓存的元素
distributed:该策略不适用于单机,是用于分布式的。
-->
</ehcache>
d)指定使用ehcache二级缓存
在映射文件mapper中的<cache />中通过type属性指定缓存机制为ehCache缓存。
默认为MyBatis内置的二级缓存org.apache.ibatis.catch.impl.PerpetualCache。
<!-- 开启mybatis自带二级缓存 或者 开启第三方缓存机制-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!-- 可以进行个性化设置,不过仅仅是对当前namespace起作用 -->
<!-- /src/ehcache.xml 是对全局起作用 -->
<property name="timeToIdleSeconds" value="60"/>
</cache>
e)开启二级缓存
mybatis.xml主配置文件
<settings>
<!-- 二级缓存 总开关 , 不写表示默认开启 -->
<setting name="cacheEnabled" value="true"/>
</settings>