頻繁的數據庫操做是很是耗費性能的(主要是由於對於DB而言,數據是持久化在磁盤中的,所以查詢操做須要經過IO,IO操做速度相比內存操做速度慢了好幾個量級),尤爲是對於一些相同的查詢語句,徹底能夠把查詢結果存儲起來,下次查詢一樣的內容的時候直接從內存中獲取數據便可,這樣在某些場景下能夠大大提高查詢效率。算法
SqlSession>DefaultSqlSession(selectList)>this.executor.query>Executor(一級緩存BaseExecutor, 二級緩存CachingExecutor)sql
CacheKey判斷兩次查詢條件是否一致。數據庫
一級緩存:/executor/BaseExecutor.class
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(this.closed) { throw new ExecutorException("Executor was closed."); } else { if(this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null?(List)this.localCache.getObject(key):null; if(list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if(this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if(this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if(this.closed) { throw new ExecutorException("Executor was closed."); } else { CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId());//判斷id屬性是否相同 cacheKey.update(Integer.valueOf(rowBounds.getOffset()));//判斷Offset屬性是否相同 cacheKey.update(Integer.valueOf(rowBounds.getLimit()));//判斷Limit屬性是否相同 cacheKey.update(boundSql.getSql());//判斷sql屬性是否相同 List parameterMappings = boundSql.getParameterMappings();//後面都是判斷參數是否相同 TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); for(int i = 0; i < parameterMappings.size(); ++i) { ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i); if(parameterMapping.getMode() != ParameterMode.OUT) { String propertyName = parameterMapping.getProperty(); Object value; if(boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if(parameterObject == null) { value = null; } else if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = this.configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } return cacheKey; } }
即只要兩次查詢知足以上三個條件且沒有定義flushCache="true",那麼第二次查詢會直接從MyBatis一級緩存PerpetualCache中返回數據,而不會走DB。緩存
MyBatis二級緩存的生命週期即整個應用的生命週期,應用不結束,定義的二級緩存都會存在在內存中。mybatis
從這個角度考慮,爲了不MyBatis二級緩存中數據量過大致使內存溢出,MyBatis在配置文件中給咱們增長了不少配置例如size(緩存大小)、flushInterval(緩存清理時間間隔)、eviction(數據淘汰算法)來保證緩存中存儲的數據不至於太過龐大。app
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache();//Cache是從MappedStatement中獲取到的,而MappedStatement又和每個<insert>、<delete>、<update>、<select>綁定並在MyBatis啓動的時候存入Configuration中: if(cache != null) { this.flushCacheIfRequired(ms);//根據flushCache=true或者flushCache=false判斷是否要清理二級緩存 if(ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql);//保證MyBatis二級緩存不會存儲存儲過程的結果 List list = (List)this.tcm.getObject(cache, key);//tcm裝飾器模式,建立一個事物緩存TranactionalCache,持有Cache接口,Cache接口的實現類就是根據咱們在Mapper文件中配置的<cache>建立的Cache實例 if(list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); }//若是沒有從MyBatis二級緩存中拿到數據,那麼就會查一次數據庫,而後放到MyBatis二級緩存中去 return list; } }
//優先讀取以上二級緩存,query方法優先讀取默認實現的一級緩存。
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MyBatis支持三種類型的二級緩存:性能
<settings>//mybatis.cfg.xml <!-- 開啓二級緩存 默認值爲true --> <setting name="cacheEnabled" value="true"/> </settings> <mapper namespace="cn.sxt.vo.user.mapper">//mapper.xml <!-- 開啓本mapper namespace下的二級緩存 --> <cache></cache>
select a.col1, a.col2, a.col3, b.col1, b.col2, b.col3 from tableA a, tableB b where a.id = b.id;
對於tableA與tableB的操做定義在兩個Mapper中,分別叫作MapperA與MapperB,即它們屬於兩個命名空間,若是此時啓用緩存:ui
此時問題就來了,即便第(2)步tableB更新了col1與col2兩個字段,第(3)步MapperA走二級緩存查詢到的這6個字段依然是原來的這6個字段的值,由於咱們從CacheKey的3組條件來看:this
對於MapperA來講,其中的任何一個條件都沒有變化,天然會將原結果返回。spa
這個問題對於MyBatis的二級緩存來講是一個無解的問題,所以使用MyBatis二級緩存有一個前提:
必須保證全部的增刪改查都在同一個命名空間下才行。