主要內容:redis
二級緩存構建在一級緩存之上,在收到查詢請求時,MyBatis 首先會查詢二級緩存。若二級緩存未命中,再去查詢一級緩存。與一級緩存不一樣,二級緩存和具體的命名空間綁定,一級緩存則是和 SqlSession 綁定。算法
在按照 MyBatis 規範使用 SqlSession 的狀況下,一級緩存不存在併發問題。二級緩存則否則,二級緩存可在多個命名空間間共享。這種狀況下,會存在併發問題,所以須要針對性去處理。除了併發問題,二級緩存還存在事務問題。sql
配置項數據庫
<configuration>
<settings>
<setting name="cacheEnabled" value="true|false" />
</settings>
</configuration>
cacheEnabled=true表示二級緩存可用,可是要開啓話,須要在Mapper.xml內配置。緩存
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
或者 簡單方式
<cache/>
對配置項屬性說明:mybatis
在Configuration類的newExecutor方法中是否開啓二級緩存併發
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//是否開啓二級緩存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
二級緩存經過CachingExecutor來實現的,原理是緩存裏存在,就返回,不存在就調用Executor ,若是一級緩存未關閉,則先查一級緩存,不存在,再到數據庫中查詢。app
下面使用一張圖來表示:ide
下面是源碼:ui
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 得到 BoundSql 對象
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 建立 CacheKey 對象
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 查詢
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 調用 MappedStatement#getCache() 方法,得到 Cache 對象,
//即當前 MappedStatement 對象的二級緩存。
Cache cache = ms.getCache();
if (cache != null) { // <2>
// 若是須要清空緩存,則進行清空
flushCacheIfRequired(ms);
//當 MappedStatement#isUseCache() 方法,返回 true 時,才使用二級緩存。默認開啓。
//可經過@Options(useCache = false) 或 <select useCache="false"> 方法,關閉。
if (ms.isUseCache() && resultHandler == null) { // <2.2>
// 暫時忽略,存儲過程相關
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);
}
也是使用的是BaseExecutor類中的createCacheKey方法生成的,因此二級緩存key和一級緩存生成規則是同樣的。
二級緩存有一個很是重要的空間劃分策略:
namespace="com.tian.mybatis.mappers.UserMapper"
namespace="com.tian.mybatis.mappers.RoleMapper"
即,按照namespace劃分,同一個namespace,同一個Cache空間,不一樣的namespace,不一樣的Cache空間。
好比:
在這個namespace下的二級緩存是同一個。
每當執行insert、update、delete,flushCache=true時,二級緩存都會被清空。
SqlSession sqlSession = sqlSessionFactory.openSession();
System.out.println("第一次查詢");
User user = sqlSession.selectOne("com.tian.mybatis.mapper.UserMapper.selectById", 1);
System.out.println(user);
//sqlSession.commit();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
System.out.println("第二次查詢");
User user2 = sqlSession1.selectOne("com.tian.mybatis.mapper.UserMapper.selectById", 1);
System.out.println(user2);
由於二級緩存使用的是TransactionalCaheManager(tcm)來管理的,最後又調用了TranscatinalCache的getObject()、putObject()、commit方法。
TransactionalCache裏面又持有真正的Cache對象,好比:通過層層裝飾的PrepetualCache。
在putObject的時候,只是添加到entriesToAddOnCommit裏面。
//TransactionalCache類中
@Override
public void putObject(Object key, Object object) {
// 暫存 KV 到 entriesToAddOnCommit 中
entriesToAddOnCommit.put(key, object);
}
只有conmit方法被調用的時候,纔會調用flushPendingEntries方法,真正寫入到緩存裏。DefaultSqlSession調用commit方法的時候就會調到這個commit方法。
//TransactionalCache類中
public void commit() {
//若是 clearOnCommit 爲 true ,則清空 delegate 緩存
if (clearOnCommit) {
delegate.clear();
}
// 將 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
flushPendingEntries();
// 重置
reset();
}
private void flushPendingEntries() {
// 將 entriesToAddOnCommit 刷入 delegate 中
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
// 將 entriesMissedInCache 刷入 delegate 中
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void reset() {
// 重置 clearOnCommit 爲 false
clearOnCommit = false;
// 清空 entriesToAddOnCommit、entriesMissedInCache
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
由於在CachingExecutor的update方法中
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 是否須要清空緩存
//經過 @Options(flushCache = Options.FlushCachePolicy.TRUE) 或 <select flushCache="true"> 方式,
//開啓須要清空緩存。
if (cache != null && ms.isFlushCacheRequired()) {
//調用 TransactionalCache#clear() 方法,清空緩存。
//注意,此時清空的僅僅,當前事務中查詢數據產生的緩存。
//而真正的清空,在事務的提交時。這是爲何呢?
//仍是由於二級緩存是跨 Session 共享緩存,在事務還沒有結束時,不能對二級緩存作任何修改。
tcm.clear(cache);
}
}
關於多個namespace的緩存共享的問題,可使用來解決。
好比:
<cache-ref namespace="com.tian.mybatis.mapper.RoleMapper"
cache-ref表明引用別名的命名空間的Cache配置,兩個命名空間的操做使用的是同一個Cache。在關聯的表比較少或者按照業務能夠對錶進行分組的時候可使用。
「注意」:在這種狀況下,多個mapper的操做都會引發緩存刷新,因此這裏的緩存的意義已經不是很大了。
Mybatis除了自帶的二級換之外,咱們還能夠經過是想Cache接口來自定義二級緩存。
添加依賴
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
redis基礎配置項
host=127.0.0.1
port=6379
connectionTimeOut=5000
soTimeout=5000
datebase=0
在咱們的UserMapper.xml中添加
<cache type="org.mybatis.caches.redis.RedisCache"
eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
RedisCache類圖,Cache就是Mybatis中緩存的頂層接口。
對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用mybatis二級緩存技術下降數據庫訪問量,提升訪問速度,業務場景好比:耗時較高的統計分析sql、電話帳單查詢sql等。
先查二級緩存,不存在則堅持一級緩存是否關閉,沒關閉,則再查一級緩存,還不存在,最後查詢數據庫。
二級緩存開啓方式有兩步:
第一步:在全局配置中添加配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
第二步,在Mapper中添加配置
<cache type="org.mybatis.caches.redis.RedisCache"
eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
二級換是默認開啓的,可是針對每個Mapper的二級緩存是須要手動開啓的。
二級緩存的key和一級緩存的key是同樣的。
每當執行insert、update、delete,flushCache=true時,二級緩存都會被清空。
咱們能夠繼承第三方緩存來做爲Mybatis的二級緩存。
本文先從總體分析了Mybatis的緩存體系結構,而後就是對每一個緩存實現類的源碼進行分析,有詳細的講述一級緩存和二級緩存,如何開啓關閉,緩存的範圍的說明,緩存key是如何生成的,對應緩存是何時會被清空,先走二級緩存在走以及緩存,二級緩存使用第三方緩存。
參考:http://www.tianxiaobo.com/2018/08/25/
推薦閱讀