1. 前言
使用缓存可以使应用更快地获取数据,避免频繁的数据库交互,尤其是在查询越多、缓存命中率越高的情况下,使用缓存的作用就越明显。MyBatis 作为持久化框架,提供了非常强大的查询缓存特性,可以非常方便地配置和定制使用
如下图所示:a1和a2引用的是同一个对象
MyBatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果某次查询中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map缓存对象中已经存在该键值时,则会返回缓存中的对象
2. 一级缓存
一级缓存(也叫本地缓存,localCache)默认会启用
存在于SqlSession(这里的session指的是DefaultSqlSession,而不是SqlSessionTemplate)的生命周期中。
2.1 直接使用sqlSession获取Dao
这种方式Dao中的sqlSession为DefaultSqlSession,而不是SqlSessionTemplate
如下图所示:
MapperMethod中的sqlSession就是firstLevelCacheTest()中的sqlSession
查询数据时,先生成相应的key,再从localCache中获取结果,从缓存中取值的代码在BaseExecutor中的query()方法中
将结果放入缓存
2.2 清除缓存
2.2.1 任何的 insert、update、delete操作都会清空缓存,这三类语句都会调用update(),然后再让executor执行,最终会在executor中清空缓存
执行update语句前,先回调用clearLocalCache()方法清空缓存
2.1.2 配置flushCache="true"也会清空缓存
<select id="selectXXX" flushCache="true" resultMap="xxxMap">
在查询数据前清空当前的一级缓存。这个配置也会清空二级缓存,下面会提到
2.3 MyBatis在Spring中的一级缓存
在Spring中,自动注入dao,dao中的sqlSession为sqlSessionTemplate,所以每次执行时需要获取DefaultSqlSession
开启事务后,同一事务是同一个SqlSession,所以缓存不失效。
第一次查询时将SqlSession绑定到事务中,后面的查询直接从事务管理器中获取SqlSession
不开启事务,每查询获取新的SqlSession,所以缓存会失效
2.4 缓存范围
一级缓存的范围有两种,statement和session。配置如下
mybatis:
configuration:
local-cache-scope: statement
默认是session。如果缓存范围是statement,会清空缓存
3. 二级缓存
MyBatis的二级缓存是和命名空间绑定的,即二级缓存需要配置在mapper.xml映射文件中,或者配置在 mapper.java 接口中。在映射文件中,命名空间就是 XML 根节点 mapper 的namespace属性。在mapper接口中,命名空间就是接口的全限定名称
开启二级缓存后,返回值需要实现序列化接口,否则会报错
3.1 配置
全局开关, 默认为true, 所以可以不用配置
mybatis-config.xml
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
springboot中配置
mybatis:
configuration:
cache-enabled: true
在全局开关打开的情况下,需要在命名空间中开启
mapper.xml中开启
<cache/>
在mapper.java接口中开启
@CacheNamespace(eviction = LruCache.class,flushInterval = 0L,size = 1024, readWrite = true)
public interface ADao extends CommonMapper<A> {
}
注解中的值可以不添加,用默认值。
3.2 不使用缓存
在命名空间缓存开启的情况下,可以通过设置 useCache="false"来让某个方法不使用缓存
注意:这里不会清空缓存,只是查询不使用缓存。
<select id="getById" useCache="false" resultType="com.shopping.demo24.model.A">
select * from t_a where id = #{id}
</select>
当缓存不为空时,判断useCache属性的值是否为true,如果是true就从缓存中获取。
3.3 从缓存中获取结果
从MappedStatement中获取缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
不同的查询,ms始终是一个对象
如果开启事务,tcm就是同一个对象,但不影响缓存的获取,因为缓存是在cache中,tcm只是将cache对象缓存在自己本身的对象里而已。实际的缓存是在ms.getCache()中
3.4 缓存结果
tcm.putObject(cache, key, list)
先将结果放入TransactionalCache中的entriesToAddOnCommit中
提交的时候再将结果刷到缓存
3.5 清除缓存
3.5.1 配置flushCache="true"清除缓存
如果statement配置flushCache=“true”,则会在查询前清除二级缓存
<select id="getById" flushCache="true" resultType="com.shopping.demo24.model.A">
select * from `t_a` where id = #{id}
</select>
如果没有配置flushCache=“true”。select对应的statement中的flushCacheRequired为false,所以不会清空
3.5.2 update语句
在不配置flushCache="true"的情况下,update(包含insert和delete) flushCacheRequired的值为true,所以会清空缓存
先将clearOnCommit设为true
再在提交时清空缓存
3.6 缓存替换策略
MappedStatement中缓存淘汰策略默认用的是LRU
4. 缓存的命中顺序
缓存命中顺序:二级缓存-> 一级缓存-> 数据库
再次注意:flushCache="true"会清空一级和二级缓存