一級緩存基於 PerpetualCache 的 HashMap 本地緩存,其存儲做用域爲 Session,當 Session flush 或 close 以後,該Session中的全部 Cache 就將清空。java
二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,不一樣在於其存儲做用域爲 Mapper(Namespace),而且可自定義存儲源,如 Ehcache、Hazelcast等。算法
對於緩存數據更新機制,當某一個做用域(一級緩存Session/二級緩存Namespaces)的進行了 C/U/D 操做後,默認該做用域下全部 select 中的緩存將被clear。spring
MyBatis 的緩存採用了delegate機制 及 裝飾器模式設計,當put、get、remove時,其中會通過多層 delegate cache 處理,其Cache類別有:BaseCache(基礎緩存)、EvictionCache(排除算法緩存) 、DecoratorCache(裝飾器緩存):sql
通常緩存框架的數據結構基本上都是 Key-Value 方式存儲,MyBatis 對於其 CacheKey 的生成採起規則爲:數據庫
[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。apache
對於併發 Read/Write 時緩存數據的同步問題,MyBatis 默認基於 JDK/concurrent中的ReadWriteLock,使用 ReentrantReadWriteLock 的實現,從而經過 Lock 機制防止在併發 Write Cache 過程當中線程安全問題。緩存
測試 User user=userService.get(55); 安全
2、Executor框架數據結構
解析器:結合mybatis-spring框架,讀取spring關於mybatis的配置文件。具體看是否開啓緩存(這裏指二級緩存),若是開啓,生成的執行器爲CachingExecutor。 mybatis
動態代理:實現調用mapper接口的時候執行mybatis邏輯
執行器:執行緩存處理邏輯。在這裏二級緩存和一級緩存有所區別。
BatchExcutor、ReuseExcutor、 SimpleExcutor: 這幾個就沒什麼好說的了,繼承了 BaseExcutor 的實現其 doQuery、doUpdate 等方法,一樣都是採用 JDBC 對數據庫進行操做;三者區別在於,批量執行、重用 Statement 執行、普通方式執行。具體應用及場景在Mybatis 的文檔上都有詳細說明。
CachingExecutor: 二級緩存執行器。我的以爲這裏設計的不錯,靈活地使用 delegate機制。其委託執行的類是 BaseExcutor。 當沒法從二級緩存獲取數據時,一樣須要從 DB 中進行查詢,因而在這裏能夠直接委託給 BaseExcutor 進行查詢。其大概流程爲:
流程爲: 從二級緩存中進行查詢 -> [若是緩存中沒有,委託給 BaseExecutor] -> 進入一級緩存中查詢 -> [若是也沒有] -> 則執行 JDBC 查詢,其 query 代碼以下:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); // 當前 Statement 是否啓用了二級緩存 if (cache != null) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { // 未找到緩存,很委託給 BaseExecutor 執行查詢 list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } // 沒有啓動用二級緩存,直接委託給 BaseExecutor 執行查詢 return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // 將建立 cache key 委託給 BaseExecutor 建立 return this.delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); }
Cache 委託鏈構建:
正如最開始的緩存概述所描述道,其緩存類的設計採用 裝飾模式,基於委託的調用機制。
緩存實例構建:
緩存實例的構建 ,Mybatis 在解析其 Mapper 配置文件時就已經將該實現初始化,在 org.apache.ibatis.builder.xml.XMLMapperBuilder 類中能夠看到:
private void cacheElement(XNode context) throws Exception { if (context != null) { // 基礎緩存類型 String type = context.getStringAttribute("type", "PERPETUAL"); Class typeClass = typeAliasRegistry.resolveAlias(type); // 排除算法緩存類型 String eviction = context.getStringAttribute("eviction", "LRU"); Class evictionClass = typeAliasRegistry.resolveAlias(eviction); // 緩存自動刷新時間 Long flushInterval = context.getLongAttribute("flushInterval"); // 緩存存儲實例引用的大小 Integer size = context.getIntAttribute("size"); // 是不是隻讀緩存 boolean readWrite = !context.getBooleanAttribute("readOnly", false); Properties props = context.getChildrenAsProperties(); // 初始化緩存實現 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); } }
如下是 useNewCache 方法實現:
public Cache useNewCache(Class typeClass, Class evictionClass, Long flushInterval, Integer size, boolean readWrite, Properties props) { typeClass = valueOrDefault(typeClass, PerpetualCache.class); evictionClass = valueOrDefault(evictionClass, LruCache.class); // 這裏構建 Cache 實例採用 Builder 模式,每個 Namespace 生成一個 Cache 實例 Cache cache = new CacheBuilder(currentNamespace) // Builder 前設置一些從XML中解析過來的參數 .implementation(typeClass) .addDecorator(evictionClass) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .properties(props) // 再看下面的 build 方法實現 .build(); configuration.addCache(cache); currentCache = cache; return cache; } public Cache build() { setDefaultImplementations(); // 建立基礎緩存實例 Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); // 緩存排除算法初始化,並將其委託至基礎緩存中 for (Class<? extends Cache> decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } // 標準裝飾器緩存設置,如LoggingCache之類,一樣將其委託至基礎緩存中 cache = setStandardDecorators(cache); // 返回最終緩存的責任鏈對象 return cache; }
最終生成後的緩存實例對象結構:
可見,全部構建的緩存實例已經經過責任鏈方式將其串連在一塊兒,各 Cache 各負其責、依次調用,直到緩存數據被 Put 至 基礎緩存實例中存儲。
Cache 實例解剖:
實例類:SynchronizedCache
說 明:用於控制 ReadWriteLock,避免併發時所產生的線程安全問題。
解 剖:
對於 Lock 機制來講,其分爲 Read 和 Write 鎖,其 Read 鎖容許多個線程同時持有,而 Write 鎖,一次能被一個線程持有,若是當 Write 鎖沒有釋放,其它須要 Write 的線程只能等待其釋放才能去持有。
其代碼實現:
其具體原理能夠看看 jdk concurrent 中的 ReadWriteLock 實現。
實例類:LoggingCache
說 明:用於日誌記錄處理,主要輸出緩存命中率信息。
解 剖:
說到緩存命中信息的統計,只有在 get 的時候才須要統計命中率:
public Object getObject(Object key) { requests++; // 每調用一次該方法,則獲取次數+1 final Object value = delegate.getObject(key); if (value != null) { // 命中! 命中+1 hits++; } if (log.isDebugEnabled()) { // 輸出命中率。計算方法爲: hits / requets 則爲命中率 log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); } return value; }
實例類:SerializedCache
說 明:向緩存中 put 或 get 數據時的序列化及反序列化處理。
解 剖:
序列化在Java裏面已是最基礎的東西了,這裏也沒有什麼特殊之處:
public void putObject(Object key, Object object) { // PO 類須要實現 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); } } public Object getObject(Object key) { Object object = delegate.getObject(key); // 獲取數據時對 二進制數據進行反序列化 return object == null ? null : deserialize((byte[]) object); }
其 serialize 及 deserialize 代碼:
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 var4) { throw new CacheException("Error serializing object. Cause: " + var4, var4); } } private Serializable deserialize(byte[] value) { try { ByteArrayInputStream bis = new ByteArrayInputStream(value); ObjectInputStream ois = new SerializedCache.CustomObjectInputStream(bis); Serializable result = (Serializable)ois.readObject(); ois.close(); return result; } catch (Exception var5) { throw new CacheException("Error deserializing object. Cause: " + var5, var5); } }
實例類:LruCache
說 明:最近最少使用的:移除最長時間不被使用的對象,基於LRU算法。
解 剖:
這裏的 LRU 算法基於 LinkedHashMap 覆蓋其 removeEldestEntry 方法實現。好象以前看過 XMemcached 的 LRU 算法也是這樣實現的。
初始化 LinkedHashMap,默認爲大小爲 1024 個元素:
public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); // 設置 map 默認大小 } public void setSize(final int size) { // 設置其 capacity 爲size, 其 factor 爲.75F keyMap = new LinkedHashMap(size, .75F, true) { // 覆蓋該方法,當每次往該map 中put 時數據時,如該方法返回 True,便移除該map中使用最少的Entry // 其參數 eldest 爲當前最老的 Entry protected boolean removeEldestEntry(Map.Entry eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); //記錄當前最老的緩存數據的 Key 值,由於要委託給下一個 Cache 實現刪除 } return tooBig; } }; } public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); // 每次 put 後,調用移除最老的 key } // 看看當前實現是否有 eldestKey, 有的話就調用 removeObject ,將該key從cache中移除 private void cycleKeyList(Object key) { keyMap.put(key, key); // 存儲當前 put 到cache中的 key 值 if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } public Object getObject(Object key) { keyMap.get(key); // 便於 該 Map 統計 get該key的次數 return delegate.getObject(key); }
實例類:PerpetualCache
說 明:這個比較簡單,直接經過一個 HashMap 來存儲緩存數據。因此沒什麼說的,直接看下面的 MemcachedCache 吧。