Mybatis中共有三级缓存,其中一级缓存默认开启,作用范围是在sqlSession对象(同一个会话),二级缓存需要手动配置开启,作用范围是在sqlSessionFactory对象下的同一个namespace范围(所以二级缓存是可以多个会话共享的,不过如果多表关联查询,会失效)。三级缓存不用太关注。
二级缓存配置
- mybatis-config.xml全局配置,不配置也可以,默认true。
<setting name="cacheEnabled" value="true"/>
- 在对应的mapper.xml中对< cache>标签进行配置
<!-- eviction:缓存的清除策略,当缓存达到上限后会自动触发对应算法清除缓存 flushInterval:清除缓存的时间间隔,在指定时间会自动清理缓存,单位为毫秒 size:缓存存储大小,指定保存对象的个数(List集合也算一个对象),一般不推荐将List集合放入缓存中,因为List集合较为多变,命中率较 readOnly:是否为只读,设置为true代表数据缓存只读,每次从缓存取出的对象是对象本身,执行效率较高;设置为false代表读取的对象为缓存的副本,每次取出的对象是不同的,这种操作对数据较为安全 --> <cache eviction="LRU" flushInterval="60000" size="1000"/> <!-- 还可以在select、update等标签中,指定是否将结果放入缓存或执行后是否直接清除缓存--> <!-- 使用useCashe将结果集放入缓存中 --> <select id="xxx" resultType="xxx" useCache="true"> SELECT * FROM xx </select> <!-- 针对于某些情景下,想要在插入完成后就清空缓存可以使用flushCache属性,设置为true则会在SQL执行结束后立刻清空缓存 --> <update id="update" parameterType="com.mybatis.entity.Good" flushCache="true"> UPDATE xx SET aa = #{aa} WHERE bb = #{bb} </update>
Cache
Cache
cache包下的Cache接口约定了整个缓存的规范,有不同的Cache的实现类来实现它完成不同的功能。看类中方法可发现,其实就是相当于对一个map的存取。
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
/**
* 缓存
*/
public interface Cache {
/**
* 该缓存对象的id
*/
String getId();
/**
* 向缓存中添加数据,key是CacheKey,value是查询结果
*/
void putObject(Object key, Object value);
/**
* 根据指定的key,在缓存中查找对应的结果对象
*
*/
Object getObject(Object key);
/**
* 删除key对应的缓存项
*
*/
Object removeObject(Object key);
/**
* 清空缓存
*/
void clear();
/**
* 缓存项的个数
*
*/
int getSize();
/**
* 获取读写锁
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
在调用Configuration的无参构造时,同样会对常见的Cache对象的Alias(别名)做映射处理。
public Configuration() {
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
}
接下来就根据Configuration映射的顺序,来逐一查看对应的缓存类。
PerpetualCache
PerpetualCache类代表永久缓存,用HashMap来实现对缓存的存储。
/**
*
* 永久缓存
* 一旦存入就一直保持
*
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
// Cache对象的唯一标识
private final String id;
// 记录缓存想的map对象
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
// 只要id相等就认为两个cache相同
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
除了上面的PerpetualCache,其余四个类都在org.apache.ibatis.cache.decorators包下,使用到了装饰者模式。
FifoCache
底层为了了一个LinkedList链表来记录了cache的先后顺序(每次从后添加,如果size > 1024,则从头先remove ),默认LinkedList大小为1024。除此之外,还是使用装饰者模式,调用其他的Cache对象来完成Cache的存储。
/**
* FIFO缓存
* 这个类就是维护一个FIFO链表,其他都委托给所包装的cache去做。典型的装饰模式
*
* FIFO (first in, first out) cache decorator.
*
*/
public class FifoCache implements Cache {
// 底层被装饰的底层Cache对象
private final Cache delegate;
// 用于记录key进入缓存的先后顺序
private final Deque<Object> keyList;
// 记录了缓存项的上限,超过该值,则需要清理最老的缓存项
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
this.size = 1024;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(int size) {
this.size = size;
}
@Override
public void putObject(Object key, Object value) {
// 检测并清理缓存
cycleKeyList(key);
// 添加缓存项
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
private void cycleKeyList(Object key) {
// 记录key
keyList.addLast(key);
// 如果缓存达到上限,则清理最老的缓存项
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
LruCache
和FifoCache的区别在于,它使用了LinkedHashMap来记录着Cache的顺序,在进行过期策略时,删除最少使用的Cache。
/**
* 最近最少使用缓存
* 基于 LinkedHashMap 覆盖其 removeEldestEntry 方法实现。
*
* Lru (least recently used) cache decorator.
*
* @author Clinton Begin
*/
public class LruCache implements Cache {
// 被装饰的底层cache对象
private final Cache delegate;
// 有序的hashmap,用于记录key最近的使用情况
private Map<Object, Object> keyMap;
// 记录最少被使用的缓存项的key
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(final int size) {
// 重新设置缓存大小时,会重置keyMap字段,注意LinkedHashMap构造函数的第三个参数,true表示该LinkedHashMap记录的顺序是access-order
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 当调用LinkedHashMap.put方法,会调用此方法
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
// 如果已达到缓存上线,则更新eldestKey字段,后面会删除该项
if (tooBig) {
// 把eldestKey存入实例变量
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
// 添加缓存项
delegate.putObject(key, value);
// 删除最久未使用的缓存项
cycleKeyList(key);
}
@Override
public Object getObject(Object key) {
// 修改LinkedHashMap中记录的顺序
keyMap.get(key); // touch
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyMap.clear();
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
// eldestKey不为空,表示已经达到缓存上限
if (eldestKey != null) {
// 删除最久未使用的缓存项
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
SoftCache
JVM中对象的引用关系分为强、软、弱、虚。每种引用关系在JVM进行垃圾回收时回收的标准是不同的,Mybatis的SoftCache其本质就是软引用。
public class SoftCache implements Cache {
// 在SoftCache中,最近使用的一部分缓存项不会被GC回收,这就是通过将其value添加到hardLinksToAvoidGarbageCollection集合中实现的
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// 引用队列,用于记录已经被GC回收的缓存项对应的SoftEntry对象
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
// 底层被装饰的底层Cache对象
private final Cache delegate;
// 强连接的个数,默认是256
private int numberOfHardLinks;
public SoftCache(Cache delegate) {
this.delegate = delegate;
//默认链表可以存256元素
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
// 清除已经被GC回收的缓存项
removeGarbageCollectedItems();
// 向缓存中添加缓存项
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 从缓存中查找对应的缓存项
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
// 检测缓存中是否有对应的缓存项
if (softReference != null) {
// 获取SoftReference引用的value
result = softReference.get();
// 已经被GC回收
if (result == null) {
// 从缓存中清除对应的缓存项
delegate.removeObject(key);
// 未被GC回收
} else {
// See #586 (and #335) modifications need more than a read lock
synchronized (hardLinksToAvoidGarbageCollection) {
// 缓存项的value添加到hardLinksToAvoidGarbageCollection集合中保存
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
// 超过numberOfHardLinks,将最老的缓存项从hardLinksToAvoidGarbageCollection集合中清除
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
// 清理强引用集合
hardLinksToAvoidGarbageCollection.clear();
}
// 清理被GC回收的缓存项
removeGarbageCollectedItems();
// 清除底层delegate缓存中的缓存项
delegate.clear();
}
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 遍历queueOfGarbageCollectedEntries集合
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
// 将已经被GC回收的value对象对应的缓存项清除
delegate.removeObject(sv.key);
}
}
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向value的引用是软引用,且关联了引用队列
super(value, garbageCollectionQueue);
// 强引用
this.key = key;
}
}
}
WeakCache
WeakCache同SoftCache
/**
* 弱引用缓存,可以看到代码和SoftCache如出一辙,就是SoftReference变成了WeakReference
*
* Weak Reference cache decorator.
* Thanks to Dr. Heinz Kabutz for his guidance here.
*
* @author Clinton Begin
*/
public class WeakCache implements Cache {
private final Deque<Object> hardLinksToAvoidGarbageCollection;
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
private int numberOfHardLinks;
public WeakCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems();
delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
result = weakReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
private void removeGarbageCollectedItems() {
WeakEntry sv;
while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
private static class WeakEntry extends WeakReference<Object> {
private final Object key;
private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}