一级缓存
定义:一级缓存也叫本地缓存,是在会话(SqlSession)层面进行缓存的。一级缓存是默认打开的,并没有任何设置或判断语句控制是否执行一级缓存查询、添加操作。所以,无法关闭掉一级缓存。
在DefaultSqlSession中,Executor接口实现类BaseExecutor里有个缓存对象PerpetualCache localCache。
public class PerpetualCache implements Cache {
// 对象id,如LocalCache
private final String id;
/**
* 缓存map
* key为createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)生成
* value为数据库查询出的结果集List<E>
*
*/
private Map<Object, Object> cache = new HashMap<>();
}
在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。
一级缓存的工作流程
- 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。
- 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中。
- 如果命中,则直接将缓存结果返回。
- 如果没命中,则去数据库中查询数据,得到查询结果;将key和查询到的结果分别作为key,value对存储到Cache中;将查询结果返回。
一级缓存的生命周期
- MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
- 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
- SqlSession中执行了任何一个update操作(update()、delete()、insert()),都会清空PerpetualCache对象的数据,但是该对象可以继续使用。
一级缓存范围
MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选:
- session 级别的缓存,在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中。
- statement 级别的缓存,避坑: 为了避免这个问题,可以将一级缓存的级别设为 statement 级别的,这样每次查询结束都会清掉一级缓存。
<!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 -->
<setting name="localCacheScope" value="SESSION"/>
一级缓存的不足
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。
二级缓存
定义:范围是namespace级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。
在DefaultSqlSession中,Executor接口实现类CachingExecutor里有个缓存对象TransactionalCacheManager tcm。
/**
* 用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionalCaches字段
*/
public class TransactionalCacheManager {
/**
* 二级缓存map,key和value与一级缓存map一样
*/
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
/**
* CachingExecutor调用此方法,用key查询返回value,
* @param cache
* @param key
* @return
*/
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
/**
* map.computeIfAbsent(K key,
* Function<? super K, ? extends V> mappingFunction)
* key 就是我们要用来做映射的key
* mappingFunction 就是我们的映射函数
* 这个方法返回当前与key关联的值(无论是之前已经存在的,或者是刚刚计算出来的),可能为null。
* TransactionalCache::new, 调用TransactionalCache的构造方法
* @param cache
* @return
*/
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
}
缓存对象TransactionalCacheManager里的TransactionalCache对象
public class TransactionalCache implements Cache {
//对应的二级缓存对象
private final Cache delegate;
//是否在commit时清除缓存
private boolean clearOnCommit;
//需要在commit时提交到二级缓存的数据
private final Map<Object, Object> entriesToAddOnCommit;
//缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
private final Set<Object> entriesMissedInCache;
}
开启二级缓存方法
1.全局缓存开关:mybatis配置文件中声明
<!-- mybatis-config.xml 开启二级缓存(默认是开的,这里写出来是为了方便代码维护) -->
<setting name="cacheEnabled" value="true" />
2.各个namespace下的二级缓存实例 mapper.xml
<!--Mapper.xml 中配置<cache/>标签 namespace同级 -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>
或引用其他namespace的缓存
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert()会刷新缓存。如果cacheEnabled=true,Mapper.xml没有配置标签,则不会有二级缓存,但只要配置cacheEnabled=true,基本执行器就会被装饰,还是会出现CachingExecutor包装对象。
3.<select>节点中配置useCache属性
默认为true,设置false时,二级缓存针对该条select语句不会生效
缓存的清除策略
1.LRU(mybatis默认) – 最近最少使用
Least Recently Used,移除最长时间不被使用的对象。
2.LFU - 一定时期内被访问次数最少的
Least Frequently Used,移除某段时间内被访问次数最少的
3.FIFO – 先进先出
按对象进入缓存的顺序来移除它们。
4.SOFT – 软引用
基于垃圾回收器状态和软引用规则移除对象。
5.WEAK – 弱引用