【商城系統】程式碼生成器使用
Mybatis快取
二級快取
1、二級快取的定義
二級快取也稱作是應用級快取,與一級快取不同的是它的作用範圍是整個應用,而且可以跨執行緒使用。所以二級快取有更高的命中率,適合快取一些修改比較少的資料。
2、二級快取擴充套件性需求
二級快取的生命週期是整個應用,所以必須限制二級快取的容量,在這裡mybatis使用的是溢位淘汰機制。而一級快取是會話級的生命週期非常短暫是沒有必要實現這些功能的。相比較之下,二級快取機制更加完善。
3、二級快取的結構
二級快取在結構設計上採用裝飾器+責任鏈模式
1)二級快取是如何組裝這些裝飾器的呢?
這裡我們先介紹一下CacheBuilder類顧名思義這是一個快取構建類。該類就是二級快取的構建類裡面定義了一些上圖裝飾器型別的屬性,以及構建組合這些裝飾器的行為。
原始碼分析:
private final String id; private Class<? extends Cache> implementation; private final List<Class<? extends Cache>> decorators; private Integer size; private Long clearInterval; private boolean readWrite; private Properties properties; private boolean blocking; public Cache build() { this.setDefaultImplementations(); Cache cache = this.newBaseCacheInstance(this.implementation, this.id); this.setCacheProperties((Cache)cache); if (PerpetualCache.class.equals(cache.getClass())) { Iterator var2 = this.decorators.iterator(); while(var2.hasNext()) { Class<? extends Cache> decorator = (Class)var2.next(); cache = this.newCacheDecoratorInstance(decorator, (Cache)cache); this.setCacheProperties((Cache)cache); } cache = this.setStandardDecorators((Cache)cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache((Cache)cache); } return (Cache)cache; } private void setDefaultImplementations() { if (this.implementation == null) { this.implementation = PerpetualCache.class; if (this.decorators.isEmpty()) { this.decorators.add(LruCache.class); } } } private Cache setStandardDecorators(Cache cache) { try { MetaObject metaCache = SystemMetaObject.forObject(cache); if (this.size != null && metaCache.hasSetter("size")) { metaCache.setValue("size", this.size); } if (this.clearInterval != null) { cache = new ScheduledCache((Cache)cache); ((ScheduledCache)cache).setClearInterval(this.clearInterval); } if (this.readWrite) { cache = new SerializedCache((Cache)cache); } Cache cache = new LoggingCache((Cache)cache); cache = new SynchronizedCache(cache); if (this.blocking) { cache = new BlockingCache((Cache)cache); } return (Cache)cache; } catch (Exception var3) { throw new CacheException("Error building standard cache decorators. Cause: " + var3, var3); } }
4、SynchronizedCache執行緒同步快取區
實現執行緒同步功能,與序列化快取區共同保證二級快取執行緒安全。若blocking=false關閉則SynchronizedCache位於責任鏈的最前端,否則就位於BlockingCache後面而BlockingCache位於責任鏈的最前端,從而保證了整條責任鏈是執行緒同步的。
原始碼分析:只是對於操作快取的方法進行了執行緒同步功能
5、LoggingCache統計命中率以及列印日誌
統計二級快取命中率並輸出列印,由以下原始碼可知:日誌中出現了“Cache Hit Ratio”便表示命中了二級快取。
原始碼分析:
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(this.getId()); } public Object getObject(Object key) { ++this.requests;//執行一次查詢加一次 Object value = this.delegate.getObject(key);//查詢快取中是否已經存在 if (value != null) { ++this.hits;//命中一次加一次 } if (this.log.isDebugEnabled()) {//開啟debug日誌 this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio()); } return value; } private double getHitRatio() {//計算命中率 return (double)this.hits / (double)this.requests;//命中次數:查詢次數 } }
6、ScheduledCache過期清理快取區
@CacheNamespace(flushInterval=100L)設定過期清理時間預設1個小時,
若設定flushInterval為0代表永遠不進行清除。
原始碼分析:操作快取時都會進行檢查快取是否過期
public class ScheduledCache implements Cache {
private final Cache delegate;
protected long clearInterval;
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = 3600000L;
this.lastClear = System.currentTimeMillis();
}
public void clear() {
this.lastClear = System.currentTimeMillis();
this.delegate.clear();
}
private boolean clearWhenStale() {
//判斷當前時間與上次清理時間差是否大於設定的過期清理時間
if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
this.clear();//一旦進行清理便是清理全部快取
return true;
} else {
return false;
}
}
}
7、LruCache(最近最少使用)防溢位快取區
內部使用連結串列(增刪比較快)實現最近最少使用防溢位機制
原始碼分析:
public void setSize(final int size) {
this.keyMap = new LinkedHashMap<Object, Object>(size, 0.75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
boolean tooBig = this.size() > size;
if (tooBig) {
LruCache.this.eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
//每次訪問都會遍歷一次key進行重新排序,將訪問元素放到連結串列尾部。
public Object getObject(Object key) {
this.keyMap.get(key);
return this.delegate.getObject(key);
}
8、FifoCache(先進先出)防溢位快取區
原始碼分析:內部使用佇列儲存key實現先進先出防溢位機制。
ublic 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();
this.size = 1024;
}
public void putObject(Object key, Object value) {
this.cycleKeyList(key);
this.delegate.putObject(key, value);
}
public Object getObject(Object key) {
return this.delegate.getObject(key);
}
private void cycleKeyList(Object key) {
this.keyList.addLast(key);
if (this.keyList.size() > this.size) {//比較當前佇列元素個數是否大於設定值
Object oldestKey = this.keyList.removeFirst();//移除佇列頭元素
this.delegate.removeObject(oldestKey);//根據移除元素的key移除快取區中的對應元素
}
}
}
9、二級快取的使用(命中條件)
1)會話提交後
2)sql語句、引數相同
3)相同的statementID
4)RowBounds相同
注意:設定為自動提交事務並不會命中二級快取。
10、二級快取的配置
11、二級快取為什麼要提交之後才能命中快取?
會話一與會話二原本是兩條隔離的事務,但由於二級快取的存在導致彼此可見會發生髒讀。若會話二的修改直接填充到二級快取,會話一查詢時快取中存在即直接返回資料,此時會話二回滾會話一讀到的資料就是髒資料。為了解決這一問題mybatis二級快取機制就引入了事務管理器(暫存區),所有變動的資料都會暫時存放到事務管理器的暫存區中,只有執行提交動作後才會真正的將資料從暫存區中填充到二級快取中。
1)會話:事務快取管理器:暫存區=1:1:N
2)暫存區:快取區=1:1(一個暫存區對應唯一一個快取區)
3)會話關閉,事務快取管理器也會關閉,暫存區也會被清空
4)一個事務快取管理器管理多個暫存區
5)有多少個暫存區取決於你訪問了多少個Mapper檔案(快取的key是Mapper檔案全路徑ID)
12、二級快取執行流程
1)查詢是實時查詢快取區的。
2)所有對二級快取的實時變動都是通過暫存區來實現的。
3)暫存區清理完會進行標識,但此時二級快取中資料並未清理,只有執行commit後才會真正清理二級快取中的資料。
4)查詢會實時查詢快取區,若暫存區清理標識為true就算從快取區中查詢到資料也會返回一個null,重新查詢資料庫(暫存區清理標識為true也會返回null是為了防止髒讀,一旦提交清空掉二級快取中的資料此時讀取到的就是髒資料,因此返回null重新查詢資料庫得到的才是正確資料)。
原始碼分析:
a1、若開啟二級快取進行查詢方法的時候會走到類CachingExecutor中的query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//獲得Cache
Cache cache = ms.getCache();
if (cache != null) {
//判斷是否配置了flushCache=true,若配置了清空暫存區
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//若為空查詢資料庫並將資料填充到暫存區
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//跟進去
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
a2、根據上一步中的tcm.getObject(cache, key)方法查詢二級快取
public Object getObject(Object key) {
// issue #116
//查詢二級快取
Object object = delegate.getObject(key);
//為空也是為了先設定一個值防止快取穿透
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
//判斷暫存區清空標識是否為true,若為true直接返回null重新查詢資料庫防止髒讀
if (clearOnCommit) {
return null;
} else {
return object;
}
}