MyBatis二级缓存
缓存,缓冲存储是MyBatis中用于数据优化提高我们程序执行效率的方式。
- 一级缓存默认开启,缓存范围SqlSession会话
- 二级缓存手动开启,属于范围Mapper Namespace
缓存的范围
MyBatis中这两个命名空间中的所有对象都被存储到JVM的内存中。MyBatis存储对象本质就使用了Map键值对的结构来保存的。
默认情况下SqlSession的缓存也就是一级缓存是开启的,SqlSession中进行的查询结果都会被保存下来,默认的存储周期都是存储在当前的sqlSession中,也就意味着每一个用户所得到的一级缓存可能都是不一样的。当这个会话被释放的时候存储在里面的一级缓存对象都会被清空。 作为SqlSession对象存储一级缓存时间是非常短的,也就意味着存储在这里的一级缓存使用率不高,也可能会浪费额外的存储空间。
为了解决这个问题,MyBatis提供了二级缓存,二级缓存不属于SqlSession,它的作用范围更大,属于整个namespace命名空间,存储在二级缓存对象会被所有的SqlSession共享。
二级缓存运行规则
- 二级开启后默认所有查询操作均使用缓存
- 写操作commit提交时对该namespace缓存强制清空
- 配置useCache=false可以不用缓存
- 配置flushCache=true代表强制清空缓存
测试一级缓存
@Test
public void testLv1Cache() throws Exception {
SqlSession session = null;
try {
session = MybatisUtils.openSession();
RepositoryStock stock = session.selectOne("stock.selectById",20);
RepositoryStock stock1 = session.selectOne("stock.selectById",20);
System.out.println(stock.getMaterialName());
System.out.println(stock.hashCode()+":"+stock1.hashCode());
}catch (Exception e){
throw e;
}finally {
MybatisUtils.closeSession(session);
}
}
调用了两次stock.selectById但是在日志中只打印了一个SQL,虽然得到两个变量但是指向的是相同的内存区域
中间加commit(),日志中打印出两条sql语句
@Test
public void testLv1Cache() throws Exception {
SqlSession session = null;
try {
session = MybatisUtils.openSession();
RepositoryStock stock = session.selectOne("stock.selectById",20);
session.commit();
RepositoryStock stock1 = session.selectOne("stock.selectById",20);
System.out.println(stock.getMaterialName());
System.out.println(stock.hashCode()+":"+stock1.hashCode());
}catch (Exception e){
throw e;
}finally {
MybatisUtils.closeSession(session);
}
}
开启二级缓存
在RepositoryMapper.xml中的< mapper>标签下添加标签
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
测试代码
@Test
public void testLv2Cache() throws Exception {
SqlSession session = null;
try {
session = MybatisUtils.openSession();
RepositoryStock stock = session.selectOne("stock.selectById",20);
System.out.println(stock.getMaterialName());
System.out.println(stock.hashCode());
}catch (Exception e){
throw e;
}finally {
MybatisUtils.closeSession(session);
}
SqlSession session1 = null;
try {
session1 = MybatisUtils.openSession();
RepositoryStock stock = session1.selectOne("stock.selectById",20);
System.out.println(stock.getMaterialName());
System.out.println(stock.hashCode());
}catch (Exception e){
throw e;
}finally {
MybatisUtils.closeSession(session1);
}
}
运行结果只执行了一次SQL,说明配置的二级缓存生效了
Cache Hit Ratio [stock]: 0.5意思是对于[stock]这个对象,缓存的命中率为0.5
二级缓存配置
cache二级缓存标签的属性
eviction
eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
- LRU – 最近最久未使用:移除最长时间不被使用的对象。
O1 O2 O3 O4 … O512
14 99 83 1 893 - FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾收集器状态和软引用规则的对象。
- WEAK – 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval
间隔多长时间自动清理缓存,单位:毫秒
flushInterval=“600000” ,600000毫秒=10分钟
每隔10分钟对缓存进行全局清除,这个时间可以设置时间长一点
size
缓存长度,最多可以缓存多少个对象,如果缓存的是实体类代表缓存一个对象,如果查询结果返回的是一个List集合的话也只会算作是一个对象。
个人建议,在缓存中只需保留单个的实体对象缓存,不要去保留大量的List查询结果这样缓存命中率比较低。相对的如果是selectById根据id号返回唯一的对象,把他保存到缓存中的做法比较好。
size的长度不要太小,比如当前的表有1200多条数据,那么在内存足够的情况下size应该大于1200,意味着所有库存实体对象都会在二级缓存中进行保存。这样做的好处就是,在按id进行查询时会直接从内存中提取,效率是非常非常高的。
readOnly
是否是只读的
设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
设置为false,代表每次取出的是缓存对象的“副本”,每一次取出的对象都是不同的,这种安全性较高
实际开发中大多设置为true就可以了
其他标签中的缓存设置
useCache
是否使用缓存,对于selectAll查询所有的这种形式不推荐把查询结果放入缓存,因为全局查询的时候数据量实在是太大了。
useCache="false"代表查询结果不被放入缓存
<select id="selectAll" resultType="com.zl.mybatis.entity.RepositoryStock" useCache="false">
无论一级缓存二级缓存,当执行了commit()的时候当前的缓存都会被清空。但是在某些特定的需求场景下希望执行完insert语句立马清空缓存,而不是等待commit()执行完再清空。这时候可以使用flushCache=“true”,在SQL执行完以后立即清空缓存
<insert id="insert" parameterType="com.zl.mybatis.entity.RepositoryStock" flushCache="true">
这个配置不只可以放到写数据语句中,也可以放到查询中。执行完这句SQL后缓存就会被清空,清空完缓存这条SQL的执行结果同样不会被放入缓存。
<select id="selectStockMap" resultType="java.util.LinkedHashMap" flushCache="true">