MyBatis的二級緩存
阿新 • • 發佈:2018-04-22
ret put highlight locking serial lse linked pre ase
目錄
-
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擴展增加功能。下面對其裝飾器源碼進行分析
/** * 簡單阻塞裝飾器 * * 當前緩存中不存在時對緩存緩存的key加鎖,其它線程就只能一直等到這個元素保存到緩存中 * 由於對每個key都保存了鎖對象,如果在大量查詢中使用可能存在OOM都風險 * @author Eduardo Macarron * */ 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; } }
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; } } }
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); } } }
MyBatis的二級緩存