mybatis源码学习------缓存模块
简介
在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层。
MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要注意的是,MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个 JVM 中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品
缓存模块的类图
缓存接口
public interface Cache {
//获取当前缓存对象的id
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;
}
}
缓存实现
PerpetualCache
永久内存实现,底层使用使用线程不安全的HashMap作为缓存对象,只是对Map接口api的简单包装
public class PerpetualCache implements Cache {
private final String id;
//使用HashMap作为缓存
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) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
//同一个缓存实例
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
//如果两个缓存实例的id相同,也视为相等
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
/**
* id的hashcode
**/
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
LoggingCache
支持打印日志的缓存,与PerpetualCache不同的是增加了请求计数和命中计数属性,并会在debug日志级别时将这两个属性的值打印,与缓存有关的操作委派给了delegate属性中的缓存对应
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);
//如果get到了值,则对命中计数加一
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();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
//获取缓存的命中率
//命中率 = 命中次数/请求次数
private double getHitRatio() {
return (double) hits / (double) requests;
}
}
BlockingCache
阻塞缓存实现
一般情况下,当在缓存中获取不到数据后,都会向缓存中put值。所以BlockingCache会阻塞后续所有请求该缓存的请求,直到当前线程添加完缓存对象,防止多个线程同时填加同一个缓存对象。
public class BlockingCache implements Cache {
//超时时间
private long timeout;
//包装的缓存实例
private final Cache delegate;
//缓存key 与 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) {
//获取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();
}
private ReentrantLock getLockForKey(Object key) {
//从locks属性中获取缓存key对应的可重入锁对象
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
//尝试获取锁资源,超时时间为timeout
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;
}
}
SynchronizedCache
同步缓存,只是单纯的通过synchronized关键字的方式把所有方法变成同步的,简单粗暴
/**
* @author Clinton Begin
*/
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();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
}
SerializedCache
支持序列化值的 Cache 实现类
如果缓存的value没有实现Serializable接口,则抛出异常
如果缓存的value实现了Serializable接口,则将其序列化为二级制流
/**
* @author Clinton Begin
*/
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();
}
@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);
}
//重写了ObjectInputStream#resolveClass方法
//以mybatis定义的类加载器的优先级去获取对应的Class对象
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
return Resources.classForName(desc.getName());
}
}
}
ScheduledCache
定期清理所有缓存,本质上是对过期键的一种删除策略
在学习ScheduledCache代码之前,应该知道常见的过期键删除策略如下
1、定时删除,创建一个定时器,定期的扫描缓存中key是否过期,当到达过期时间时,立即将其删除。对内存友好,对cpu不友好
2、懒删除,也就是说即使缓存已经到了过期时间,也不会立即删除,只是在下次获取该缓存的时候进行判断,如果已经过期则对其删除。对cpu友好,对内存不友好。
3、定期删除,定期删除是对定时删除策略的一种优化,通过定义一个定时器,每次扫描部分缓存的方式达到删除过期key的目的,一个合理的定期删除策略可以达到对cpu友好,对内存友好的双赢结果
ScheduledCache的删除策略是定期删除的一种,只不过比较暴力。。。
/**
* @author Clinton Begin
*
* 定期清理所有缓存
*/
public class ScheduledCache implements Cache {
private final Cache delegate;
//清除时间间隔,默认为一小时
protected long clearInterval;
//上次清除缓存的时间戳
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = TimeUnit.HOURS.toMillis(1);
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);
}
private boolean clearWhenStale() {
//直接清空所有的数据
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
}
FifoCache
基于队列的缓存
常见的内存淘汰策略如下(基于redis的策略):
1、volatile-lru:在设置了过期时间的数据中,淘汰最近最少使用的数据
2、volatile-ttl:在设置了过期时间的数据中,淘汰马上到期的数据
3、volatile-random:在设置了过期时间的数据中,随机淘汰数据
4、allkeys-lru:在所有数据中,淘汰最近最少使用的数据
5、allkeys-random:在所有数据中,随机淘汰数据
6、no-enviction:不淘汰数据,内存不够就报错
7、还有就是FifoCache这种的策略,基于队列,淘汰最老的数据
public class FifoCache implements Cache {
private final Cache delegate;
//双端队列,队列中保存着缓存中所有的key,按照时间顺序入队
private final Deque<Object> keyList;
//队列大小,默认队列大小为1024
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();//将key出队
delegate.removeObject(oldestKey);//删除对应的缓存
}
}
}
LruCache
基于LinkedHashMap实现的一个Lru缓存,原理可以参考https://www.cnblogs.com/mengheng/p/3683137.html的内容
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); // 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);
//每次操作值的时候会将修改过的Entry移到连表的最后
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
WeakCache
弱引用缓存
public class WeakCache implements Cache {
//强引用的队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//已经被GC回收的Entry,当GC通过可达性分析发现对象已经不可达,需要被回收时,会将对象加入到ReferenceQueue
//队列的实例中,等待GC回收
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 {//表示当前对象还在
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();
}
//将缓存中已经被GC判定为GC Root不可达的对象删除
private void removeGarbageCollectedItems() {
WeakEntry sv;
//循环删除被GC清除的所有缓存对象
while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
//弱引用,用于包装缓存中的key和value,会在GC下一次收集的时候被GC从堆中删除
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;
}
}
}
SoftCache
软引用缓存
public class SoftCache implements Cache {
//强引用的队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//已经被GC回收的Entry,当GC通过可达性分析发现对象已经不可达,需要被回收时,会将对象加入到ReferenceQueue
//队列的实例中,等待GC回收
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
//强引用的最大个数
private int numberOfHardLinks;
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") // assumed delegate cache is totally managed by this cache
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
// See #586 (and #335) modifications need more than a read lock
//没看懂为什么在软引用时需要加锁,而在弱引用时却不需要,上面的issue搜了一下,一脸懵逼的进去,一脸 //懵逼出来
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();
}
//将缓存中已经被GC判定为GC Root不可达的对象删除
private void removeGarbageCollectedItems() {
SoftEntry sv;
//循环删除被GC清除的所有缓存对象
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
//软引用,用于包装缓存中的key和value,会在内存不足的时候被GC从堆中删除
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;
}
}
}
CacheKey
缓存键对象,由多个对象组成。所以 CacheKey 可以理解成将多个对象放在一起,计算其缓存键。
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
//null对应的key
public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
@Override
public void update(Object object) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
@Override
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
};
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
//生成后的hashCode
private int hashcode;
//校验和
private long checksum;
//updateList集合的size()
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.
//
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
//checksum 等于数组中所有对象的baseHashCode的和
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
@Override
public boolean equals(Object object) {
//是内存中的同一个对象,肯定相等
if (this == object) {
return true;
}
//连CacheKey的实例都不是,一定不相等
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
//如果两个CacheKey对象的hashCode值不相等,则一定不是同一个key
if (hashcode != cacheKey.hashcode) {
return false;
}
//如果两个CacheKey对象的校验和值不相等,则一定不是同一个key
if (checksum != cacheKey.checksum) {
return false;
}
//如果两个CacheKey对象的对象总数不相等,则一定不是同一个key
if (count != cacheKey.count) {
return false;
}
//上面所有的都相等时,再比较updateList集合中的每一个对象是否相等
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() {
//tringJoiner是Java8新出的一个类,用于构造由分隔符分隔的字符序列,
//并可选择性地从提供的前缀开始和以提供的后缀结尾。省的我们开发人员再次通过StringBuffer
//或者StingBuilder拼接。
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 clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
推荐阅读
-
MyBatis源码解析 - 反射模块
-
springboot mybatis 项目框架源码 shiro 集成代码生成器 ehcache缓存
-
MyBatis学习笔记(九)缓存
-
Mybatis源码学习(15)-binding模块之MapperMethod类
-
mybatis源码学习------resultMap和sql片段的解析
-
mybatis源码学习------动态sql的解析(SqlSource)
-
mybatis源码解析之基础模块-log
-
Mybatis 源码阅读----事务模块(Transaction)
-
MyBatis源码解析(二) 基础支持层反射模块Reflector
-
mybatis源码解析之基础模块-TypeHandler