MyBatis的缓存分为一级缓存和二级缓存,一级缓存是 SqlSession 级别的缓存,二级缓存是mapper级别的缓存。但是这篇博客主要是介绍mybaits中缓存接口和缓存键接口,以及一些缓存实现。
之前写过一篇博客简单介绍了一下Hibernate的两级缓存。
链接: https://blog.csdn.net/Let_me_...
源码位置: org.apache.ibatis.cache.Cache
缓存容器接口,自定义操作方法,其他缓存实现类需要实现这个接口。
上图中列出来的就是Cache接口的实现类,实现不同的缓存功能。
接口的源码很简单,就是定义了一些增删改查缓存的方法,和一个获取容器中缓存数量和获得读写锁的方法。我精简了下注释,源码内容如下。Cache接口其实是一个缓存容器,有点类似于一个HashMap(有一些实现类就是使用HashMap来保存操作缓存数据的)。
public interface Cache { /** * 获取标识 */ String getId(); /** * 添加指定的键 */ void putObject(Object key, Object value); /** * 获取指定的键的值 */ Object getObject(Object key); /** * 删除指定的键的值 */ Object removeObject(Object key); /** * 清空缓存 */ void clear(); /** * 获取容器中缓存的数量 */ int getSize(); /** * 获得读写锁 */ default ReadWriteLock getReadWriteLock() { return null; } }
源码位置: org.apache.ibatis.cache.impl.PerpetualCache
永不过期的缓存,使用HashMap来保存和操作数据,重写了 equals()
和 hashCode()
方法,其他缓存操作都是直接调用的HashMap方法。
源码位置: org.apache.ibatis.cache.decorators.LoggingCache
这是一个支持打印日志的 Cache 实现,代码也很简单,加了一些注释。
public class LoggingCache implements Cache { /** * mybaits log 对象 */ private final Log log; /** * 装饰的 Cache 对象 */ private final Cache delegate; /** * 统计请求缓存的次数 */ protected int requests = 0; /** * 命中缓存的次数 */ protected int hits = 0; public LoggingCache(Cache delegate) { this.delegate = delegate; this.log = LogFactory.getLog(getId()); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public void putObject(Object key, Object object) { delegate.putObject(key, object); } @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; } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } /** * 计算命中比例 * @return */ private double getHitRatio() { //算法: 命中次数 / 请求缓存次数 return (double) hits / (double) requests; } }
源码位置: org.apache.ibatis.cache.decorators.BlockingCache
阻塞的Cache实现类,这个实现不同的逻辑是在加锁上。当一个线程去获取缓存事,缓存不存在则会阻塞后续线程获取,当前线程则去添加缓存值,避免后续线程重复添加缓存。需要注意的是这个实现里 removeObject
方法并不是删除缓存值,而是移除锁。
public class BlockingCache implements Cache { /** * 阻塞等待超时时间 */ private long timeout; /** * 装饰的Cache对象 */ private final Cache delegate; /** * 缓存键 与 ReentrantLock 对象映射 */ private final ConcurrentHashMap<Object, ReentrantLock> locks; public BlockingCache(Cache delegate) { this.delegate = delegate; this.locks = new ConcurrentHashMap<>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @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; } @Override public void clear() { delegate.clear(); } /** * 获得 ReentrantLock 对象,如果不存在,则进行添加 * @param key * @return */ private ReentrantLock getLockForKey(Object key) { return locks.computeIfAbsent(key, k -> new ReentrantLock()); } private void acquireLock(Object key) { //获得键对应的 ReentrantLock 对象 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) { //获得 ReentrantLock 对象 ReentrantLock lock = locks.get(key); if (lock.isHeldByCurrentThread()) { //如果当前线程持有锁,进行释放 lock.unlock(); } } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } }
源码位置: org.apache.ibatis.cache.decorators.SynchronizedCache
同步Cache实现,内部也是使用装饰的Cache来实现缓存操作。不过这个实现在 getSize
putObject
getObject
removeObject
clear
这几个方法上添加了 synchronized
关键字。
源码位置: org.apache.ibatis.cache.decorators.SerializedCache
支持序列化值,其实就是在添加缓存的时候对值进行序列化,获取值的时候反序列化。
public class SerializedCache implements Cache { //装饰的 Cache private final Cache delegate; public SerializedCache(Cache delegate) { this.delegate = delegate; } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @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); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } 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); } } 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; } public static class CustomObjectInputStream extends ObjectInputStream { public CustomObjectInputStream(InputStream in) throws IOException { super(in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { return Resources.classForName(desc.getName()); } } }
源码位置: org.apache.ibatis.cache.decorators.ScheduledCache
定时清空整个 Cache 的缓存,在每次操作缓存之前判断是否全部清空缓存
public class ScheduledCache implements Cache { /** * 装饰的 Cache 对象 */ private final Cache delegate; /** * 清空间隔,单位:毫秒 */ protected long clearInterval; /** * 最后清空时间,单位:毫秒 */ protected long lastClear; public ScheduledCache(Cache delegate) { this.delegate = delegate; //默认清空间隔 一小时 this.clearInterval = 60 * 60 * 1000; // 1 hour this.lastClear = System.currentTimeMillis(); } public void setClearInterval(long clearInterval) { this.clearInterval = clearInterval; } @Override public String getId() { return delegate.getId(); } @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) { //判断是否需要全部清空 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); } /** * 判断是否需要全部清空 * @return */ private boolean clearWhenStale() { if (System.currentTimeMillis() - lastClear > clearInterval) { //全部清空 clear(); return true; } return false; } }
源码位置: org.apache.ibatis.cache.decorators.FifoCache
基于先进先出淘汰机制的Cache实现,此实现在删除缓存时并不会删除缓存key,所以旧的 key 也依旧会继续存在。
除此之外在添加key的时候该实现也不会去判断key是否已经存在,只会判断当前长度是否超过了队列上上限,所以重复添加就会在队列里存在多个相同的key,这个不能说是bug,只能说是允许重复的key,但是如果你在使用中发现同一个key拿到的缓存不止一个,可能就需要检查一下你使用的是不是 FifoCache
实现了。
public class FifoCache implements Cache { /** * 装饰的 Cache 对象 */ private final Cache delegate; /** * 双端队列,记录键值的添加 */ 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) { //循环 keyList cycleKeyList(key); delegate.putObject(key, value); } @Override public Object getObject(Object key) { return delegate.getObject(key); } @Override public Object removeObject(Object key) { //删除缓存,未清空keyList中的数据 return delegate.removeObject(key); } @Override public void clear() { //清空缓存时,同时清空维护的 keyList delegate.clear(); keyList.clear(); } private void cycleKeyList(Object key) { //添加到 keyList keyList.addLast(key); if (keyList.size() > size) { //如果添加新 key 之后 ketList 的长度大于 队列上线,将队列的首位移除 Object oldestKey = keyList.removeFirst(); delegate.removeObject(oldestKey); } } }
源码位置: org.apache.ibatis.cache.decorators.LruCache
此实现是基于最少使用的淘汰机制的 Cache 实现,简单说就是当添加缓存时发现已经达到上限的时候,淘汰掉最少使用的 key 以及对应的缓存。使用 LinkedHashMap 的淘汰机制,具体源码的解析请参考下面的源码。
public class LruCache implements Cache { /** * 装饰的 Cache 对象 */ private final Cache delegate; /** * 基于 LinkedHashMap 实现淘汰机制 */ private Map<Object, Object> keyMap; /** * 最老/最少被使用的键,即将被淘汰的 key */ private Object eldestKey; public LruCache(Cache delegate) { this.delegate = delegate; //初始化 keyMap 对象 setSize(1024); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } /** * 初始化keyMap,但是此方法的权限标识是 public ,也就是说可以通过这个方法来指定 keyMap的长度,默认是1024,可以根据自己的需求来变更 * @param size */ public void setSize(final int size) { //LinkedHasmp的一个构造函数,当参数 accessOrder 为 true 时,即会按照访问的顺序排序,最近访问的在最前,最早访问的在最后 keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { //重写 LinkedHashMapLinkedHashMap 的删除元素方法,当满足一定条件的时候删除元素,LinkedHashMap中默认是不删除 //这里判断的条件就是 keyMap 长度大于初始化 keyMap 时给定的值,满足条件时将最少使用的 key 设置为待删除, // 等下次添加新key的时候判断 eldestKey 参数不为空则对 eldestKey 进行移除 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); } @Override public Object getObject(Object key) { 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) { //添加 key 到keyMap中 keyMap.put(key, key); //如果超过上限,则删除最少使用的可以 if (eldestKey != null) { //移除 eldestKey delegate.removeObject(eldestKey); //置空 eldestKey = null; } } }
源码位置: org.apache.ibatis.cache.decorators.WeakCache
基于 java.lang.ref.WeakReference
的Cache实现类,主要是基于内部维护的一个强引用和mybatis基于 java.lang.ref.WeakReference
扩展的WeakEntry来实现缓存淘汰。在这个实现里,一样存在key多次添加会导致重复key的问题。
public class WeakCache implements Cache { //强引用的键的队列 private final Deque<Object> hardLinksToAvoidGarbageCollection; //被GC回收的 WeakEntry 集合,避免被 GC private final ReferenceQueue<Object> queueOfGarbageCollectedEntries; //装饰的 Cache 对象 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<>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { //移除已经被 GC 回收的WeakEntry removeGarbageCollectedItems(); return delegate.getSize(); } public void setSize(int size) { this.numberOfHardLinks = size; } @Override public void putObject(Object key, Object value) { //移除已经被 GC 回收的 WeakEntry 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 对象 WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key); if (weakReference != null) { //获得值 result = weakReference.get(); if (result == null) { //值为空,表示已经被GC回收,移除缓存 delegate.removeObject(key); } else { //非空,添加到 hardLinksToAvoidGarbageCollection 队列首部,未做key唯一性判断,所以存在重复添加的情况 ,避免被 GC hardLinksToAvoidGarbageCollection.addFirst(result); if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) { //如果长度超出上限,则移除队列尾部的元素 hardLinksToAvoidGarbageCollection.removeLast(); } } } return result; } @Override public Object removeObject(Object key) { //移除已经被 GC 回收的 WeakEntry removeGarbageCollectedItems(); //移除缓存 return delegate.removeObject(key); } @Override public void clear() { //清空 hardLinksToAvoidGarbageCollection hardLinksToAvoidGarbageCollection.clear(); //移除已经被 GC 回收的 WeakEntry removeGarbageCollectedItems(); //清空缓存 delegate.clear(); } /** * 移除已经被 GC 回收的键 */ private void removeGarbageCollectedItems() { WeakEntry sv; while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) { delegate.removeObject(sv.key); } } /** * 继承自 WeakReference ,增加缓存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; } } }
源码位置: org.apache.ibatis.cache.decorators.SoftCache
基于 java.lang.ref.SoftReference
的Cache实现,SoftCache内部实现了SoftEntry,其他基本上与WeakCache基本上是差不多的,只是在一些操作中会对 hardLinksToAvoidGarbageCollection 加锁。
public class WeakCache implements Cache { //强引用的键的队列 private final Deque<Object> hardLinksToAvoidGarbageCollection; //被GC回收的 WeakEntry 集合,避免被 GC private final ReferenceQueue<Object> queueOfGarbageCollectedEntries; //装饰的 Cache 对象 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<>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { //移除已经被 GC 回收的WeakEntry removeGarbageCollectedItems(); return delegate.getSize(); } public void setSize(int size) { this.numberOfHardLinks = size; } @Override public void putObject(Object key, Object value) { //移除已经被 GC 回收的 WeakEntry 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 对象 WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key); if (weakReference != null) { //获得值 result = weakReference.get(); if (result == null) { //值为空,表示已经被GC回收,移除缓存 delegate.removeObject(key); } else { //非空,添加到 hardLinksToAvoidGarbageCollection 队列首部,未做key唯一性判断,所以存在重复添加的情况 ,避免被 GC hardLinksToAvoidGarbageCollection.addFirst(result); if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) { //如果长度超出上限,则移除队列尾部的元素 hardLinksToAvoidGarbageCollection.removeLast(); } } } return result; } @Override public Object removeObject(Object key) { //移除已经被 GC 回收的 WeakEntry removeGarbageCollectedItems(); //移除缓存 return delegate.removeObject(key); } @Override public void clear() { //清空 hardLinksToAvoidGarbageCollection hardLinksToAvoidGarbageCollection.clear(); //移除已经被 GC 回收的 WeakEntry removeGarbageCollectedItems(); //清空缓存 delegate.clear(); } /** * 移除已经被 GC 回收的键 */ private void removeGarbageCollectedItems() { WeakEntry sv; while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) { delegate.removeObject(sv.key); } } /** * 继承自 WeakReference ,增加缓存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; } } }
源码位置: org.apache.ibatis.cache.CacheKey
mybaits中的缓存键,不只是单纯的String字符串,而是由多个对象组成,共同计算缓存键。CacheKey中封装了多个影响缓存的属性。
public class CacheKey implements Cloneable, Serializable { private static final long serialVersionUID = 1146682552656046210L; //单例 空缓存键 public static final CacheKey NULL_CACHE_KEY = new NullCacheKey(); //multiplier 的值 private static final int DEFAULT_MULTIPLYER = 37; //hashcode 的值 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. //计算 hashcode 的对象的集合 private List<Object> updateList; public CacheKey() { this.hashcode = DEFAULT_HASHCODE; this.multiplier = DEFAULT_MULTIPLYER; this.count = 0; this.updateList = new ArrayList<>(); } public CacheKey(Object[] objects) { this(); //基于 objects ,更新相关属性 updateAll(objects); } public int getUpdateCount() { return updateList.size(); } public void update(Object object) { //方法参数 object 的 hashcode int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; //checksum 为 baseHashCode 求和 checksum += baseHashCode; //计算hashcode值 baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; //添加 object(缓存key) 到updateList中 updateList.add(object); } public void updateAll(Object[] objects) { //遍历 objects 数组,调用 update 方法,更新相关属性 for (Object o : objects) { update(o); } } @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) { return false; } if (checksum != cacheKey.checksum) { return false; } if (count != cacheKey.count) { return false; } for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; } @Override public int hashCode() { return hashcode; } @Override public String toString() { StringJoiner returnValue = new StringJoiner(":"); returnValue.add(String.valueOf(hashcode)); returnValue.add(String.valueOf(checksum)); updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add); return returnValue.toString(); } @Override public CacheKey clone() throws CloneNotSupportedException { //克隆 CacheKey 对象 CacheKey clonedCacheKey = (CacheKey) super.clone(); //创建 updateList 数组,避免原数组修改 clonedCacheKey.updateList = new ArrayList<>(updateList); return clonedCacheKey; } }
源码位置: org.apache.ibatis.cache.NullCacheKey
继承自 CacheKey ,空缓存键。
public final class NullCacheKey extends CacheKey { private static final long serialVersionUID = 3704229911977019465L; public NullCacheKey() { super(); } @Override public void update(Object object) { throw new CacheException("Not allowed to update a NullCacheKey instance."); } @Override public void updateAll(Object[] objects) { throw new CacheException("Not allowed to update a NullCacheKey instance."); } }public final class NullCacheKey extends CacheKey { private static final long serialVersionUID = 3704229911977019465L; public NullCacheKey() { super(); } @Override public void update(Object object) { throw new CacheException("Not allowed to update a NullCacheKey instance."); } @Override public void updateAll(Object[] objects) { throw new CacheException("Not allowed to update a NullCacheKey instance."); } }