MyBatis源码阅读–缓存
前言
MyBatis提供了缓存机制减轻数据库压力,提高数据库性能。
MyBatis的缓存分为两级:一级缓存、二级缓存。
一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效。
二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的。
一级缓存
一级缓存是默认打开的,不能够进行更改。
实例
log.info("执行第一个SqlSession.........");
SysUser sysUser1 = null;
SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
try {
sysUserMapper = sqlSession1.getMapper(SysUserMapper.class);
log.info("执行第一次查询.........");
sysUser1 = sysUserMapper.selectByPrimaryKey(1030L);
log.info("查询结果 sysUser-1 = " + sysUser1);
log.info("执行第二次查询.........");
SysUser sysUser2 = sysUserMapper.selectByPrimaryKey(1030L);
log.info("查询结果 sysUser-2 = " + sysUser2);
log.info("sysUser1和sysUser2是否是同一个对象?---" + (sysUser1 == sysUser2));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
sqlSession1.close();
}
log.info("执行第二个SqlSession.........");
SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
try {
sysUserMapper = sqlSession2.getMapper(SysUserMapper.class);
log.info("执行第三次查询.........");
SysUser sysUser3 = sysUserMapper.selectByPrimaryKey(1030L);
log.info("查询结果 sysUser-3 = " + sysUser3);
log.info("执行第四次查询.........");
SysUser sysUser4 = sysUserMapper.selectByPrimaryKey(1030L);
log.info("查询结果 sysUser-4 = " + sysUser4);
log.info("sysUser3和sysUser4是否是同一个对象?---" + (sysUser3 == sysUser4));
log.info("sysUser1和sysUser3是否是同一个对象?---" + (sysUser1 == sysUser3));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
sqlSession2.close();
}
输出
INFO [main] - 执行第一个SqlSession.........
INFO [main] - 执行第一次查询.........
DEBUG [main] - ==> Preparing: select id, user_name, user_password, user_email,
create_time, user_info, head_img from sys_user where id = ?
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <== Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <== Total: 1
INFO [main] - 查询结果 sysUser-1 = SysUser(id=1030, userName=liqia, userPassword=789,
userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - 执行第二次查询.........
INFO [main] - 查询结果 sysUser-2 = SysUser(id=1030, userName=liqia, userPassword=789,
userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - sysUser1和sysUser2是否是同一个对象?---true
INFO [main] - 执行第二个SqlSession.........
INFO [main] - 执行第三次查询.........
DEBUG [main] - ==> Preparing: select id, user_name, user_password, user_email, create_time,
user_info, head_img from sys_user where id = ?
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <== Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <== Total: 1
INFO [main] - 查询结果 sysUser-3 = SysUser(id=1030, userName=liqia, userPassword=789,
userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - 执行第四次查询.........
INFO [main] - 查询结果 sysUser-4 = SysUser(id=1030, userName=liqia, userPassword=789,
userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
INFO [main] - sysUser1和sysUser3是否是同一个对象?---false
从上面可以看出,由于一级缓存的存在,当查询条件一样的时候,MyBatis第二次查询并没有去执行SQL语句,而是直接使用第一次查询时缓存中数据。
并且两次查询的对象是同一个,说明第一次查询之后,其将查询结果进行了缓存。
sysUser1和sysUser3是两个不同的对象,因为是不同的SqlSession,不同的SqlSession之间的缓存是无效的。
当对该数据表进行任何的INSERT,UPDATE,DELETE操作时,都会清空缓存。
二级缓存
二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的。
配置二级缓存
第一步:MyBatis配置文件配置cacheEnabled
查看类Configuration可以看出,MyBatis二级缓存默认是打开的。
public Configuration() {
.....................................
this.cacheEnabled = true;
......................................
}
也可以在配置文件中配置启用或禁用缓存。
<settings>
<!-- 这个配置使全局的映射器启用或禁用缓存 -->
<setting name="cacheEnabled" value="true" />
</settings>
第二步:Mapper.xml文件配置 节点
<?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.mybatis.dao.mapper.SysUserMapper" >
<!-- -->
<cache/>
</mapper>
二级缓存配置完成。
<cache/>
这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句将会被缓存。
- 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
- 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不* 干扰其他调用者或线程所做的潜在修改。
指定参数
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。
1.eviction(可用的回收策略)
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
2.flushInterval:刷新间隔,默认不设置,即没有刷新间隔,缓存仅仅在 调用语句时刷新。
3.size:引用数目。
4.readOnly:只读的缓存会给调用者返回缓存对象的相同实例,因此这些对象不能被修改,这个提供了很重要的性能优势。可读写的缓存会通过序列化返回对象的拷贝,这种方式会慢一些,但是安全。默认是false.
Cache是在MapperBuilderAssistant类中进行创建的。
org.apache.ibatis.builder.MapperBuilderAssistant
Cache cache = (new CacheBuilder(this.currentNamespace))
.implementation((Class)this.valueOrDefault(typeClass, PerpetualCache.class))
. addDecorator((Class)this.valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval)
. size(size).readWrite(readWrite).blocking(blocking).properties(props).build();
cache节点是在org.apache.ibatis.builder.xml.XMLMapperBuilder类中进行解析的,如果对应的参数为null,则会设置相应的默认值。
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);
//LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
测试
二级缓存配置完成以后,运行上面的代码。
INFO [main] - 执行第一个SqlSession........
INFO [main] - 执行第一次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.0
DEBUG [main] - ==> Preparing: select id, user_name, user_password, user_email, create_time, user_info, head_img from sys_user where id = ?
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <== Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <== Total: 1
INFO [main] - 查询结果 sysUser-1 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - 执行第二次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.0
INFO [main] - 查询结果 sysUser-2 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - sysUser1和sysUser2是否是同一个对象?---true
INFO [main] - 关闭sqlSession1
INFO [main] - 执行第二个SqlSession.........
INFO [main] - sqlSession1 == sqlSession2 ? false
INFO [main] - 执行第三次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.3333333333333333
INFO [main] - 查询结果 sysUser-3 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - 执行第四次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.5
INFO [main] - 查询结果 sysUser-4 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
INFO [main] - sysUser1和sysUser3是否是同一个对象?---true
可以看到输出结果中多了这条语句,也就是缓存命中率。
Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.3333333333333333
由于只有在执行sqlSession1.close();后,才会更新二级缓存。
所以第一次和第二次查询的命中率都是0;
第三次为0.3333,因为查询3次,命中一次。
第四次为0.5,因为查询4次,命中两次。
当设置为可读写缓存
<cache readOnly="false"/>
输出为
INFO [main] - sysUser3和sysUser4是否是同一个对象?---false
INFO [main] - sysUser1和sysUser3是否是同一个对象?---false
当设置为只读缓存
<cache readOnly="true"/>
输出为
INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
INFO [main] - sysUser1和sysUser3是否是同一个对象?---true
这是因为设置为只读缓存时,使用map来保存对象,多次读取到的都是同一个对象。
当设置为可读写缓存,使用序列化来实现缓存,多次获取对象都是通过反序列化得到,因此是不同的缓存对象,但是对象的值是一样的。