目录
Mybatis中如何配置二级缓存
基于注解配置缓存
基于XML配置缓存
Cache解析处理过程
Cache支持的过期策略
缓存的基本实现
装饰器模式
Mybatis缓存实现源码分析
BlockingCache
LRU缓存实现
FIFO缓存
序列化缓存
软引用缓存
定时缓存
Mybatis配置二级缓存有两种方式,分别是基于注解配置和基于XML配置文件配置。
@CacheNamespace(blocking=true)
public interface PersonMapper {
@Select("select id, firstname, lastname from person")
public List findAll();
}
为什么配置了一个
private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取配置的type值,默认值为PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
//获取type的class
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//获取配置过期策略,默认值为LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//获取配置的刷新间隔
Long flushInterval = context.getLongAttribute("flushInterval");
//获取配置的缓存大小
Integer size = context.getIntAttribute("size");
//是否配置了只读,默认为false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//是否配置了阻塞,默认为false
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
public class PerpetualCache implements Cache {
//缓存ID
private final String id;
//缓存
private Map
在Mybatis中是不是根据不通的过期策略都创建不通都缓存呢?实际上Mybatis的所有Cache算法都是基于装饰器模式对PerpetualCache扩展增加功能。下面简单介绍一下装饰器(Decorator)模式以及在Mybatis装饰器实现源码
装饰器模式以客户透明对方式动态给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰器模式可以在不创造更多子类的情况下对对象对功能加以扩展。
装饰器模式常常被称为包裹模式,就是因为每一个装饰器实现类,都是将下一个装饰器或真实实现包裹起来。这样做可以将真实实现简化逻辑,同时更容易扩展新功能。
Mybatis缓存实现就是运用了装饰器模式,下面我们通过源码分析看看是如何使用的。
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
//在cache基础上加上ScheduledCache装饰器
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
//如果配置只读则增加序列化功能
if (readWrite) {
//增加序列化装饰器
cache = new SerializedCache(cache);
}
//增加日志装饰器
cache = new LoggingCache(cache);
//增加同步装饰器
cache = new SynchronizedCache(cache);
if (blocking) {
//增加阻塞读装饰器
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
简单阻塞装饰器, 当前缓存中不存在时对缓存缓存的key加锁,其它线程就只能一直等到这个元素保存到缓存中由于对每个key都保存了锁对象,如果在大量查询中使用可能存在OOM都风险
public class BlockingCache implements Cache {
//超时时间
private long timeout;
//委派代表
private final Cache delegate;
//缓存key和锁的映射关系
private final ConcurrentHashMap locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap();
}
//获取ID,直接委派给delegate处理
@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);
//如果缓存数据存在则释放锁,否则返回,注意,此时锁没有释放;下一个线程获取的时候是没有办法
//获取锁,只能等待;记住 put结束的时候会释放锁,这里就是为什么put之前没有获取锁,但是结束后要释放锁的原因
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();
}
@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;
}
}
LRU算法主要通过LinkedHashMap实现,实现简单明了
public class LruCache implements Cache {
private final Cache delegate;
//key映射表
private Map 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) {
//使用LinedListHashMap实现LRU, accessOrder=true 会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
keyMap = new LinkedHashMap(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
//如果当前大小已经超过1024则删除最老元素
boolean tooBig = size() > size;
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) {
//每次反问都会触发keyMap的排序
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) {
//将当前key放入keyMap
keyMap.put(key, key);
//如果最老的key不为null则清除最老的key的缓存
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
Mybatis中FIFO缓存是通过LinkedList实现了的,下面我们通过源码去看看是如何实现的
public class FifoCache implements Cache {
private final Cache delegate;
//双端队列
private final Deque 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) {
//将当前key添加到队尾
keyList.addLast(key);
//如果key的队列长度超过限制则删除队首的key以及缓存
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
序列化缓存对于缓存对象进行了序列化和反序列化避免了值引用问题
/**
* 序列化缓存
*/
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) {
//如果对象为null或者实现了Serializable接口的对象需要进行序列化,否则抛出异常
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);
//如果对象为null则直接返回null,否则返回反序列化后对象
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 ReadWriteLock getReadWriteLock() {
return null;
}
@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();
oos.close();
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();
ois.close();
} 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());
}
}
}
public class SoftCache implements Cache {
//强引用队列,包装队列中的元素不会被垃圾回收
private final Deque hardLinksToAvoidGarbageCollection;
//引用队列,被回收对象在添加到引用队列中
private final ReferenceQueue 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();
//将当前键值对包装成SoftEntry存入缓存
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
//从缓存中获取软引用对象
SoftReference softReference = (SoftReference) delegate.getObject(key);
//如果软引用不为null,则获取引用对象中的真实的对象
if (softReference != null) {
result = softReference.get();
//如果真实的对象为null,则标示该对象已经被垃圾回收了,则删除缓存
if (result == null) {
delegate.removeObject(key);
} else { //真实对象不为null
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 {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}
定时缓存并不是使用主动失效方式,而是使用懒惰方式。
/**
* 定期清空缓存的装饰器,其清空缓存的策略使用的是懒惰清空方式
* 在 getSize,putObject, getObject removeObject的时候会触发清空检查
*
*/
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; // 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 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;
}
}