MyBatis的二级缓存
Posted wei_zw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis的二级缓存相关的知识,希望对你有一定的参考价值。
目录
-
Mybatis中如何配置二级缓存
-
Cache解析处理过程
- Cache支持的过期策略
- 装饰器模式
- 装饰器源码
Mybatis中如何配置二级缓存
基于注解配置缓存
@CacheNamespace(blocking=true) public interface PersonMapper { @Select("select id, firstname, lastname from person") public List<Person> findAll(); }
基于XML配置缓存
<mapper namespace="org.apache.ibatis.submitted.cacheorder.Mapper2"> <cache/> </mapper>
Cache解析处理过程
为什么配置了一个<cache/>就可以使用缓存了呢?通过下面的源码可以发现,缓存配置是有默认值的
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); } }
Cache支持的过期策略
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<Object, Object> cache = new HashMap<Object, Object>(); 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 ReadWriteLock getReadWriteLock() { return null; } @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(); } }
在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<Object, ReentrantLock> locks; public BlockingCache(Cache delegate) { this.delegate = delegate; this.locks = new ConcurrentHashMap<Object, ReentrantLock>(); } //获取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缓存,LRU算法主要通过LinkedHashMap实现,实现简单明了
public class LruCache implements Cache { private final Cache delegate; //key映射表 private Map<Object, Object> 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<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> 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; } } }
FIFO缓存,通过LinkedList实现了FIFO算法
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<Object>(); 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<Object> hardLinksToAvoidGarbageCollection; //引用队列,被回收对象在添加到引用队列中 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<Object>(); this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>(); } @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<Object> softReference = (SoftReference<Object>) 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<Object> { private final Object key; SoftEntry(Object key, Object value, ReferenceQueue<Object> 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; } }
以上是关于MyBatis的二级缓存的主要内容,如果未能解决你的问题,请参考以下文章