https://blog.csdn.net/zhurhyme/article/details/81064108sql
對於mybatis的緩存認識一直有一個誤區,因此今天寫一篇文章幫本身訂正一下。mybatis的緩存分一級緩存與二級緩存。下面分別對這兩種緩存進行詳細解說。
mybatis 的調用鏈
首先咱們須要大體的瞭解一下mybatis的調用鏈,而後才能更好的理解mybatis的緩存。
主要的api羅列以下:
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);//重點是這個方法
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
Configuration中的
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); //mybatis的插件機制
return executor;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
上面的代碼大體描述了,咱們爲了獲取一個sqlSession實例的過程。下面的方法爲SqlSession的實現類DefaultSqlSession中的一個查詢方法.
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
1
2
3
4
5
6
7
8
9
10
11
從該方法咱們可知SqlSession的方法實現所有委託給了Executor實例。Executor接口的類圖:
這裏寫圖片描述
mybatis 一級緩存
上面的executor.query如何執行呢?public abstract class BaseExecutor 中的方法給了咱們答案.
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //從一級緩存中獲取
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
請注意localCache是什麼呢?在BaseExecutor的構造器有這麼一句代碼: this.localCache = new PerpetualCache(「LocalCache」); 即locaCache爲緩存,即mybatis的一級緩存。
localCache生命週期是與SqlSession的生命週期是同樣;不一樣的sqlSession會生成不一樣的localCache.這就是mybatis的一級緩存,它是與sqlSession息息相關,只能單個sqlSession實例獨享。而且默認是開啓的,沒有開關能夠關閉它。可是它的使用很是有侷限。因此mybatis才須要在二級緩存,即突破SqlSession範圍的緩存。mybatis的二級緩存與咱們一般認知的緩存沒有區別。
mybatis 的二級緩存
mybatis的二級緩存即經過CachingExecutor來實現。
CachingExecutor 中的query方法代碼以下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache(); //這句是重點
if (cache != null) {
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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
即當cache不爲null時,則從tcm中獲取,若是獲取不到則再從delegate中獲取。
如今關鍵是tcm.getObject(cache, key);
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
TransactionalCacheManager的部分源代碼
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
/*
關鍵代碼,它的做用從transactionalCaches 中獲取TransactionalCache ,若是沒有,則將cache做爲參數,從新new一個,這段代碼涉及jdk8的新語法及以及新特性,
不太容易懂,若是有小朋友看不懂,我建議你好好學習一下jdk8
*/
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TransactionalCache 部分源碼
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
峯迴路轉
Cache cache = ms.getCache(); //這句是重點
1
即只要MappedStatement ms 本身有cache,則直接使用,這個就是mybatis的二級緩存。關於開啓mybatis的二級緩存,則須要在config中配置cacheEnabled 爲ture,它的默認值即爲true。
另外須要在mapper中配置
<cache />
1
這樣就能夠最簡單的方式使用mybatis的二級緩存了。至於如何擴展mybatis的二級緩存,不在本文之列。
mybatis 二級緩存絮叨
至此mybatis 的二級緩存好像已經說明白了。可是還想在此絮叨一下,緣由是爲了更好的理解什麼是緩存。mybatis的二級緩存cache它是跟在MappedStatement上的。咱們設置 cache標籤,是以mapper爲單位的。即這個mapper文件內的statement 共用這個cache標籤。cache它與sqlSession沒有任何關係,即不一樣的sqlSession執行同一個statement就能夠共用同一個cache實例了。
那麼是否有更大的cache使用範圍呢?答案是有的,舉個栗子。 部門表被cache;人員表被cache,從上面咱們可知,這是兩個不一樣的cache實例。在人員mapper中如何出人員與部門的聯合查詢,則當部門更新時,會出現局部的數據不一致。如何解決這個問題呢?一種答案是去掉緩存;另外一種就是mybatis給咱們提供的
<cache-ref >
1
標籤,它可使多個mapper使用同一個cache實例,從而達到緩存數據的一致性(關於緩存數據的一致性,性能,緩存數據範圍,在此不討論)。
---------------------
做者:zhurhyme
來源:CSDN
原文:https://blog.csdn.net/zhurhyme/article/details/81064108
版權聲明:本文爲博主原創文章,轉載請附上博文連接!api