Mybatis支持一级缓存和二级缓存,以及缓存默认是开启的,而二建缓存需要进行配置。这两两级缓存都依赖cache模块。
Cache模块所在的位置:
Cache模块主要用了装饰者设计模式,增强类中的方法,Cache的实现类只有一个PerpetualCache,其余皆为装饰者类。
1、Cache接口如下
public interface Cache {
/**
* 缓存标识
* @return The identifier of this cache
*/
String getId();
/**
* 添加指定键的值,类似于map的put() 方法
*/
void putObject(Object key, Object value);
/**
* 根据key获取缓存
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* 移除指定的缓存
* @param key The key
* @return Not used
*/
Object removeObject(Object key);
/**
* 清空缓存
* Clears this cache instance.
*/
void clear();
/**
* 获取容器中缓存的数量
*/
int getSize();
/**
* 默认方法
* @return A ReadWriteLock
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
其实现类PerpetualCache类如下
public class PerpetualCache implements Cache {
/**
* 缓存的唯一标识
*/
private final String id;
/**
* 存储缓存的内容
*/
private final Map<Object, Object> cache = new HashMap<>();
/**
* 构造器
* @param id
*/
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) {
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();
}
}
比较简单。
2、在decorators包中,全部都是装饰者类
1)、BlockingCache保证在某一时刻只有一个线程获取缓存中的数据,如下
**
* 超时时间
*/
private long timeout;
/**
* 装饰的cache
*/
private final Cache delegate;
/**
* 为指定key上锁的容器,即每一个key对应一个锁
*/
private final ConcurrentHashMap<Object, ReentrantLock> locks;
主要的实现方法如下
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
/**
* 释放当前线程的锁
*/
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
// 获取锁
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
// 当获取的缓存值存在时,则释放锁,否则锁将会继续被该线程持有
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
private ReentrantLock getLockForKey(Object key) {
// 将指定key上锁并存放在concurrentHashMap中
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
// 为key创建锁
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
// 当设置了超时时间,在该时间段内获取锁,若在该时间段内没获取到锁,一直处于阻塞状态
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
// 没有获取到锁,抛出异常
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
// 直接锁住
lock.lock();
}
}
private void releaseLock(Object key) {
// 根据key获取锁
ReentrantLock lock = locks.get(key);
// 判断当前线程是否持有该锁
if (lock.isHeldByCurrentThread()) {
//持有则释放
lock.unlock();
}
}
2)、LoggingCache支持打印日志的实现类,其定义属性如下
/***
* mybatis 的 log 对象
*/
private final Log log;
// 装饰者者设计模式,对cache实现类进一步进行增强
private final Cache delegate;
/**
* 请求获取缓存的次数
*/
protected int requests = 0;
/**
* 统计缓存命中次数
*/
protected int hits = 0;
其主要增强的方法为getObject(key),如下:
@Override
public Object getObject(Object key) {
// 获取缓存次数加1
requests++;
// 获取缓存内容
final Object value = delegate.getObject(key);
if (value != null) {
// 内容不为空, 命中 +1
hits++;
}
if (log.isDebugEnabled()) {
// 打印出缓存命中率
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
// 返回缓存内容
return value;
}
getHitRatio()方法用于计算命中率,如下
/**
* 命中率 = 命中数 / 总获取数
* @return
*/
private double getHitRatio() {
return (double) hits / (double) requests;
}
3)、ScheduledCache主要是周期性清除缓存,比较简单,如下
/**
* 定时时间 ms
*/
protected long clearInterval;
/**
* 上次清空时间 ms
*/
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
// 默认1小时
this.clearInterval = TimeUnit.HOURS.toMillis(1);
// 开始时,上次清空时间为当前系统时间
this.lastClear = System.currentTimeMillis();
}
@Override
public int getSize() {
// 检测是否到时间,如果到时间则清除缓存
clearWhenStale();
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
// 检测是否到时间,如果到时间则清除缓存
clearWhenStale();
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
// 若时间到了清空缓存,返回null,否则根据key进行获取
return clearWhenStale() ? null : delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
clearWhenStale();
return delegate.removeObject(key);
}
@Override
public void clear() {
lastClear = System.currentTimeMillis();
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private boolean clearWhenStale() {
// 判断当前时间减去上次清空时间是否大于定时时间,若大于,说明过期,清空
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
4)、SynchronizedCache主要是对cache实现类的方法进行了同步。如下
@Override
public String getId() {
return delegate.getId();
}
@Override
public synchronized int getSize() {
return delegate.getSize();
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
......
5)、SerializedCache主要对cache的实现类的值进行序列化,主要作用于添加缓存和获取缓存的方法
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
// 对值进行序列化
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
// 对值进行反序列化
return object == null ? null : deserialize((byte[]) object);
}
序列化和反序列化方法如下:
/**
* 序列化方法
* @param value
* @return
*/
private byte[] serialize(Serializable value) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
/**
* 反序列化方法
* @param value
* @return
*/
private Serializable deserialize(byte[] value) {
Serializable result;
try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis)) {
result = (Serializable) ois.readObject();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
}
6)、FifoCache基于队列先进先出的缓存策略,如下:
/**
* 存取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 void putObject(Object key, Object value) {
// 检测是否超出队列容量大小,并进行相关操作
cycleKeyList(key);
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
// 向尾部添加
keyList.addLast(key);
if (keyList.size() > size) {
//当队列值大于size值时,将最先缓存的数据key从队列删除
Object oldestKey = keyList.removeFirst();
// 清空缓存
delegate.removeObject(oldestKey);
}
}
7)、LruCache基于最少使用的淘汰机制实现Cache
// 保存key的map
private Map<Object, Object> keyMap;
// 最久的键
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
public void setSize(final int size) {
//最近访问的放在最前面,最早访问的放在最后面
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
/**
* 当新添加entry时,判断是否去除最旧的entry
* 在原方法中返回false,需要重写
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 当添加新的缓存是,判断是否需要进行淘汰
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
// 当eldest不为空,则从delegate移除
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
....其他方法比较简单
8)、WeakCacher通过弱引用的方式对缓存进行管理,需要与ReferenceQueue配合使用,其中定义字段如下:
/**
* 强引用键的队列,避免缓存被GC
*/
private final Deque<Object> hardLinksToAvoidGarbageCollection;
/**
* 被GC回收WeakEntry集合
*/
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
/**
* hardLinksToAvoidGarbageCollection的大小
*/
private int numberOfHardLinks;
public WeakCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
WeakCache 中缓存项的 value 是WeakEntry对象, WeakEntry继承 WeakReference其中指向key 的引用是强引用, 而指向 value 的引用是软引用 ,WeakEntry实现如下:
// 由于多了一个缓存健值,所以继承WeakReference
private static class WeakEntry extends WeakReference<Object> {
private final Object key;
private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向 value 的引用是软引用,且关联了引用队列
super(value, garbageCollectionQueue);
// 强引用
this.key = key;
}
}
getObject和putObject方法如下:
@Override
public void putObject(Object key, Object value) {
// 移除已经被GC回收的WeakEntry
removeGarbageCollectedItems();
// 添加到delegate中
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对象
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
// 若weakReference不为空
if (weakReference != null) {
// 获取值
result = weakReference.get();
if (result == null) {
// 结果为空,说明已经被GC了,从delegate中移除
delegate.removeObject(key);
} else {
// 不为空,添加到队头,防止被GC
hardLinksToAvoidGarbageCollection.addFirst(result);
// 当队列size大于指定值时,移除队尾的元素
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
return result;
}
WeakCache.removeObject() 方法在清 缓存 前,也会调用 removeGarbageCollectedltems()方法清理被 GC 回收的缓存项,比较简单。
9)SoftCache与WeakCache相似,只不过时软引用。
3、CacheKey:因为 MyBatis 中的缓存键不是一个简单的 String ,而是通过多个对象组成。所以 CacheKey 可以理解成将多个对象放在一起,计算其缓存键。
其中定义的字段如下
/**
* 参与计算hashcode,默认为37
*/
private static final int DEFAULT_MULTIPLIER = 37;
/**
*
*/
private static final int DEFAULT_HASHCODE = 17;
/**
* hashcode 求值的系数
*/
private final int multiplier;
/**
* 缓存键的hashcode
*/
private int hashcode;
/**
* 校验合
*/
private long checksum;
/**
* updateList 集合的个数
*/
private int count;
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
/**
* 由该集合中的所有对象共同决定两个 CacheKey 是否相同
*/
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
在向 acheKey.updateList 集合中添加对象时,使用的是 CacheKey.update() 方法,具体实现如下:
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
而update方法如下:
public void update(Object object) {
// 根据Object进行相应特征值的计算
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
// 添加updateList中
updateList.add(object);
}
比较简单,可以到Test中cache模块下的CacheKey类进行调试,调试一下立刻就会明白。
Cache缓存模块至此完毕!!
参考文献《Mybatis技术内幕》