1. 概述 本文,我们来分享 MyBatis 的缓存模块,对应 cache
包。如下图所示:`transaction` 包
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层。
MyBatis 中提供了一级缓存和二级缓存 ,而这两级缓存都是依赖于基础支持层中的缓 存模块实现的。这里需要读者注意的是,MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个 JVM 中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。
本文涉及的类如下图所示:类图
下面,我们就一起来看看具体的源码实现。另外,如果你对 MyBatis 的 Cache 机制不是很了解,可以简单阅读下 《MyBatis 文档 —— 缓存》 。
2. Cache org.apache.ibatis.cache.Cache
,缓存容器 接口。注意,它是一个容器,有点类似 HashMap ,可以往其中添加各种缓存 。代码如下:
public interface Cache { String getId () ; void putObject (Object key, Object value) ; Object getObject (Object key) ; Object removeObject (Object key) ; void clear () ; int getSize () ; @Deprecated ReadWriteLock getReadWriteLock () ; }
Cache 基于不同的缓存过期策略 、特性 ,有不同的实现类。下面,我们来逐个来看。可以组合多种 Cache ,实现特性的组合。这种方式,就是常见的设计模式,《【设计模式读书笔记】装饰者模式》 。
2.1 PerpetualCache org.apache.ibatis.cache.impl.PerpetualCache
,实现 Cache 接口,永不过期 的 Cache 实现类,基于 HashMap 实现类。代码如下:
public class PerpetualCache implements Cache { private final String id; private 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(); } }
2.2 LoggingCache org.apache.ibatis.cache.decorators.LoggingCache
,实现 Cache 接口,支持打印日志 的 Cache 实现类。代码如下:
public class LoggingCache implements Cache { private final Log log; 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) { requests++; final Object value = delegate.getObject(key); if (value != null ) { 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(); } private double getHitRatio () { return (double ) hits / (double ) requests; } }
delegate
属性,被装饰的 Cache 对象。
在 #getObject(Object key)
方法,增加了 requests
和 hits
的计数,从而实现命中比率的统计,即 #getHitRatio()
方法。
2.3 BlockingCache org.apache.ibatis.cache.decoratorsBlockingCache
,实现 Cache 接口,阻塞的 Cache 实现类。
这里的阻塞比较特殊,当线程去获取缓存值时,如果不存在,则会阻塞后续的其他线程去获取该缓存。 为什么这么有这样的设计呢?因为当线程 A 在获取不到缓存值时,一般会去设置对应的缓存值,这样就避免其他也需要该缓存的线程 B、C 等,重复添加缓存。
代码如下:
public class BlockingCache implements Cache { private long timeout; private final Cache delegate; 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) { releaseLock(key); return null ; } @Override public void clear () { delegate.clear(); } @Override public ReadWriteLock getReadWriteLock () { return null ; } private ReentrantLock getLockForKey (Object key) { ReentrantLock lock = new ReentrantLock(); ReentrantLock previous = locks.putIfAbsent(key, lock); return previous == null ? lock : previous; } private void acquireLock (Object 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) { ReentrantLock lock = locks.get(key); if (lock.isHeldByCurrentThread()) { lock.unlock(); } } public long getTimeout () { return timeout; } public void setTimeout (long timeout) { this .timeout = timeout; } }
locks
属性,缓存键与 ReentrantLock 对象的映射。
#getLockForKey(Object key)
方法,获得 ReentrantLock 对象。如果不存在,进行添加。
#acquireLock(Object key)
方法,锁定 ReentrantLock 对象。
#releaseLock(Object key)
方法,释放 ReentrantLock 对象。
#getObject(Object key)
方法:
<1.1>
处,首先会获得锁。这样其它线程来获取该值时,将被阻塞等待。那岂不是有问题?答案在 <1.3>
处。
<1.2>
处,然后,获得缓存值。
<1.3>
处,获得缓存值成功 时,会释放锁,这样被阻塞等待的其他线程就可以去获取缓存了。但是,如果获得缓存值失败 时,就需要在 #putObject(Object key, Object value)
方法中,添加缓存时,才会释放锁,这样被阻塞等待的其它线程就不会重复添加缓存了。
#putObject(Object key, Object value)
方法:
<2.1>
处,添加缓存。
<2.2>
处,释放锁。
#removeObject(Object key)
方法,它很特殊 ,和方法名字有所“冲突”,不会移除对应的缓存,只会移除锁。
2.4 SynchronizedCache org.apache.ibatis.cache.decorators.SynchronizedCache
,实现 Cache 接口,同步 的 Cache 实现类。代码如下:
public class SynchronizedCache implements Cache { private final Cache delegate; public SynchronizedCache (Cache delegate) { this .delegate = delegate; } @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); } @Override public synchronized Object removeObject (Object key) { return delegate.removeObject(key); } @Override public synchronized void clear () { delegate.clear(); } }
比较简单,相应的方法,添加了 synchronized
修饰符。
2.5 SerializedCache org.apache.ibatis.cache.decorators.SerializedCache
,实现 Cache 接口,支持序列化值 的 Cache 实现类。代码如下:
public class SerializedCache implements 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(); } 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 IOException, ClassNotFoundException { return Resources.classForName(desc.getName()); } } }
2.6 ScheduledCache org.apache.ibatis.cache.decorators.ScheduledCache
,实现 Cache 接口,定时清空整个容器 的 Cache 实现类。代码如下:
public class ScheduledCache implements Cache { private final Cache delegate; protected long clearInterval; protected long lastClear; public ScheduledCache (Cache delegate) { this .delegate = delegate; this .clearInterval = 60 * 60 * 1000 ; 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 ReadWriteLock getReadWriteLock () { return null ; } @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 ; } }
每次缓存操作时,都调用 #clearWhenStale()
方法,根据情况,是否清空全部缓存。
2.7 FifoCache org.apache.ibatis.cache.decorators.FifoCache
,实现 Cache 接口,基于先进先出的淘汰机制 的 Cache 实现类。代码如下:
public class FifoCache implements 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) { 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(); } @Override public ReadWriteLock getReadWriteLock () { return null ; } private void cycleKeyList (Object key) { keyList.addLast(key); if (keyList.size() > size) { Object oldestKey = keyList.removeFirst(); delegate.removeObject(oldestKey); } } }
目前 FifoCache 的逻辑实现上,有一定的问题,主要有两点。
<1>
处,如果重复添加一个缓存,那么在 keyList
里会存储两个,占用了缓存上限的两个名额。
<2>
处,在移除指定缓存时,不会移除 keyList
里占用的一个名额。
2.8 LruCache org.apache.ibatis.cache.decorators.LruCache
,实现 Cache 接口,基于最少使用的淘汰机制 的 Cache 实现类。代码如下:
public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; 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 = new LinkedHashMap<Object, Object>(size, .75F , true ) { private static final long serialVersionUID = 4267176411845948333L ; @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); } @Override public Object getObject (Object key) { keyMap.get(key); return delegate.getObject(key); } @Override public Object removeObject (Object key) { return delegate.removeObject(key); } @Override public void clear () { delegate.clear(); keyMap.clear(); } @Override public ReadWriteLock getReadWriteLock () { return null ; } private void cycleKeyList (Object key) { keyMap.put(key, key); if (eldestKey != null ) { delegate.removeObject(eldestKey); eldestKey = null ; } } }
2.9 WeakCache org.apache.ibatis.cache.decorators.WeakCache
,实现 Cache 接口,基于 java.lang.ref.WeakReference
的 Cache 实现类。代码如下:
public class WeakCache implements Cache { private final Deque<Object> hardLinksToAvoidGarbageCollection; private int numberOfHardLinks; private final ReferenceQueue<Object> queueOfGarbageCollectedEntries; private final Cache delegate; 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" ) WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key); if (weakReference != null ) { result = weakReference.get(); if (result == null ) { delegate.removeObject(key); } else { 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 () { hardLinksToAvoidGarbageCollection.clear(); removeGarbageCollectedItems(); delegate.clear(); } @Override public ReadWriteLock getReadWriteLock () { return null ; } 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; } } }
因为有些胖友,对 Java 的四种引用类型不是很熟悉,推荐先阅读下 《Java 中的四种引用类型》 ,很有趣!!!
WeakEntry ,继承 WeakReference 类,因为要多定一个 key
属性,代表缓存键 。
#removeGarbageCollectedItems()
方法,从 delegate
中移除已经被 GC 回收的 WeakEntry 。为什么能这样做呢?答案见 #putObject(Object key, Object value)
方法。
#putObject(Object key, Object value)
方法,我们可以看到,添加到 delegate
中的值是创建的 WeakEntry 对象,并且 WeakEntry 对象的 garbageCollectionQueue
属性为 queueOfGarbageCollectedEntries
。也就说,如果 WeakEntry 对象被 GC 时,就会添加到 queueOfGarbageCollectedEntries
队列中,那么 #removeGarbageCollectedItems()
方法就可以从 delegate
中移除已经被 GC 回收的 WeakEntry 。可能胖友会有点懵逼,但是这个真的非常有趣。
#getObject(Object key)
方法:
首先,从 delegate
获取键对应的 WeakReference 对象。
如果,值为空 ,说明已经被 GC 掉,只能从 delegate
中移除。
如果,值非空 ,为了避免被 GC 掉,所以添加到 hardLinksToAvoidGarbageCollection
队头 。但是,该队列设置了一个上限( numberOfHardLinks
),避免队列无限大。
另外,这里添加到 hardLinksToAvoidGarbageCollection
队头 应该是有问题的。因为,可能存在重复 添加,如果获取相同的键。
其它方法,胖友自己看看,比较简单的。
2.10 SoftCache org.apache.ibatis.cache.decorators.SoftCache
,实现 Cache 接口,基于 java.lang.ref.SoftReference
的 Cache 实现类。代码如下:
public class SoftCache implements Cache { private final Deque<Object> hardLinksToAvoidGarbageCollection; private int numberOfHardLinks; private final ReferenceQueue<Object> queueOfGarbageCollectedEntries; private final Cache delegate; public SoftCache (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 SoftEntry(key, value, queueOfGarbageCollectedEntries)); } @Override public Object getObject (Object key) { Object result = null ; @SuppressWarnings ("unchecked" ) SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key); if (softReference != null ) { result = softReference.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(); } @Override public ReadWriteLock getReadWriteLock () { return null ; } private void removeGarbageCollectedItems () { SoftEntry sv; while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null ) { delegate.removeObject(sv.key); } } private static class SoftEntry extends SoftReference <Object > { private final Object key; SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) { super (value, garbageCollectionQueue); this .key = key; } } }
实现逻辑上,和 WeakCache 是一致的,差异在使用 SoftEntry 替代了 WeakEntry 类。
3. CacheKey org.apache.ibatis.cache.CacheKey
,实现 Cloneable、Serializable 接口,缓存键。
因为 MyBatis 中的缓存键不是一个简单的 String ,而是通过多个对象组成 。所以 CacheKey 可以理解成将多个对象放在一起,计算其缓存键。
3.1 构造方法 private static final long serialVersionUID = 1146682552656046210L ;public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();private static final int DEFAULT_MULTIPLYER = 37 ;private static final int DEFAULT_HASHCODE = 17 ;private final int multiplier;private int hashcode;private long checksum;private int count;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 (); updateAll(objects); }
当构造方法的方法参数为 Object[] objects
时,会调用 #updateAll(Object[] objects)
方法,更新相关属性。
3.2 updateAll #updateAll(Object[] objects)
方法,更新相关属性。代码如下:
public void updateAll (Object[] objects) { for (Object o : objects) { update(o); } }
3.3 hashCode #hashCode()
方法,获得 hashcode
值。代码如下:
@Override public int hashCode () { return hashcode; }
3.4 equals #equals(Object object)
方法,比较是否相等。代码如下:
@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 ; }
3.5 clone #clone()
方法,克隆对象。代码如下:
@Override public CacheKey clone () throws CloneNotSupportedException { CacheKey clonedCacheKey = (CacheKey) super .clone(); clonedCacheKey.updateList = new ArrayList<>(updateList); return clonedCacheKey; }
3.6 NullCacheKey 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." ); } }
4. 二级缓存 MyBatis 的二级缓存,和执行器有很大的关联,所以放在 《精尽 MyBatis 源码分析 —— SQL 执行(一)之 Executor》 中,统一解析。
TransactionalCache
TransactionalCacheManager
666. 彩蛋 对缓存又多了解了一丢丢,hohoho 。
参考和推荐如下文章: