mybatis的緩存分爲一級緩存和二級緩存java
一級緩存:基於SqlSession級別的緩存,也就是說,緩存了這個SqlSession執行全部的select.MapperStatement的結果集;同一個查詢語句,只會請求一次;可是當前SqlSession執行增刪改操做或者commit/rollback操做時,會清空SqlSession的一級緩存;sql
禁止一級緩存(同理也禁止了二級緩存)數據庫
xml方式: <select id="ping" flushCache="true" resultType="string"> ... </select> 註解方式: @Options(flushCache = FlushCachePolicy.TRUE)
一級緩存致使的問題:每一個SqlSession可能會對同一個mapperStatement緩存不一樣的數據,如:設計模式
這致使了sqlSession1和sqlSession2對於userId=1的緩存數據不一致,引入髒數據緩存
一級緩存源代碼:安全
public abstract class BaseExecutor implements Executor { // 一級緩存 protected PerpetualCache localCache; protected BaseExecutor(Configuration configuration, Transaction transaction) { ... // 默認使用PerpetualCache this.localCache = new PerpetualCache("LocalCache"); ... } @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ... // 增刪改刪除一級緩存 clearLocalCache(); ... } @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... // flushCache=true時清空一級緩存 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } ... // 判斷一級緩存是否有值 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); } ... } @Override public boolean isCached(MappedStatement ms, CacheKey key) { // 一級緩存是否包含cachekey return localCache.getObject(key) != null; } @Override public void commit(boolean required) throws SQLException { ... // commit 刪除緩存 clearLocalCache(); ... } @Override public void rollback(boolean required) throws SQLException { ... // rollback刪除緩存 clearLocalCache(); ... } @Override public void clearLocalCache() { if (!closed) { // 清空緩存 localCache.clear(); localOutputParameterCache.clear(); } } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 刪除原來的一級緩存 localCache.removeObject(key); } // 將新獲取的值放入一級緩存 localCache.putObject(key, list); 。。。 } }
二級緩存:基於SqlSessionFactory緩存,全部SqlSession查詢的結果集都會共享;其實這樣描述是不許確的,二級緩存是基於namespace的緩存,每一個mapper對應一個全局Mapper namespace;當第一個Sqlsession查出的結果集,緩存在namespace中,第二個sqlsession再查找時會從nameSpace中獲取;每一個namespace是單例的;只有sqlSession調用了commit方法纔會生效session
禁止二級緩存:mybatis
mybatis-congif.xml:將全部的namespace都關閉二級緩存 <settings> <setting name="cacheEnabled" value="false"/> ... </settings> 對單個namespace是否使用二級緩存 <cache /> 當前namespace是否使用二級緩存 <cache-ref namespace="..." /> 當前namespace和其它namespace共用緩存 對一個namespace中的單個MapperStatement關閉二級緩存 <select id="selectParentBeans" resultMap="ParentBeanResultMap" useCache="false"> select * from parent </select>
二級緩存致使的問題:當某個namespace出現多表查詢時,會引發髒數據,如:app
此時再來查A表中的mapperStatement id = "xxx"時,仍是使用了A namespace中的二級緩存;這引發了B表id=5的髒數據ide
固然解決上述問題能夠使用<cache-ref>;但這會致使緩存粒度變粗,多個namespace的操做都會影響該緩存;
二級緩存源碼:
Configuration# public Executor newExecutor(Transaction transaction, ExecutorType executorType) { ... // 判斷是否使用二級緩存 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 對executor制定插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
public class CachingExecutor implements Executor { // 委託設計模式 private final Executor delegate; // 二級緩存 private final TransactionalCacheManager tcm = new TransactionalCacheManager(); @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { // 更新調用清空緩存方法(flushCache=false時不狀況二級緩存) flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 判斷該namespace是否含有緩存 Cache cache = ms.getCache(); if (cache != null) { // 看是否須要清空該namespace下的二級緩存 flushCacheIfRequired(ms); // 當前MapperStatement是否須要使用二級緩存 if (ms.isUseCache() && resultHandler == null) { ... list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 存入二級緩存 tcm.putObject(cache, key, list); // issue #578 and #116 ... } } } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); // flushCache=true時清空該namespace下的二級緩存,反之則不狀況緩存 if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache); } } }
public class TransactionalCacheManager { // 存儲二級緩存,以namespace的cache做爲key private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); // 清空namespace的緩存 public void clear(Cache cache) { getTransactionalCache(cache).clear(); } // 獲取某個namespace中的cacheKey的緩存 public Object getObject(Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } // 放入某個namespace cacheKey的緩存 public void putObject(Cache cache, CacheKey key, Object value) { getTransactionalCache(cache).putObject(key, value); } // 二級緩存提交 public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } // 二級緩存回滾 public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } // transactionalCaches緩存有則不存儲,沒有則存入 private TransactionalCache getTransactionalCache(Cache cache) { return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new); } }
在看源代碼的時候,transactionalCaches引發深思
其實二級緩存是以namespace粒度存儲在Mapper裏面的;每一個mapper是全局共享的;並且getTransactionalCache這個方法已經將當前的namespace存放於每一個cachingExecutor中了,因此達到了線程安全且sqlSession共享