六、MyBatis-快取機制
MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地配置和定製。快取可以極大的提升查詢效率。
MyBatis系統中預設定義了兩級快取, 一級 快取和 二級快取。
– 1、預設情況下,只有一級快取(SqlSession級別的快取,也稱為本地快取)開啟,一級快取預設實現類org.apache.ibatis.cache.impl.PerpetualCache。
– 2、二級快取需要手動開啟和配置,他是基於namespace級別的快取。
– 3、為了提高擴充套件性。MyBatis定義了快取介面Cache,我們可以通過實現Cache介面來自定義二級快取。
一級快取
一級快取是Local Cache,即本地快取,預設作用域為SqlSesson級別,mybatis3.1之後, 可以配置本地快取的作用域(SESSION|STATEMENT),在全域性配置檔案中可以設定localCacheScope。Mybatis預設的一級快取預設是通過org.apache.ibatis.cache.impl.PerpetualCache來實現的,PerpetualCache內裡有一個HashMap,用來儲存本地快取。
Map的CacheKey由方法的ID,RowBounds,SQL語句,引數,環境因素影響;Map的Value存放具體的結果物件。
格式:影響因子的HashCode1:影響因子的HashCode2:方法ID:offset:limit:SQL語句:引數:環境ID
-1623117942:1735139101:com.kancy.mapper.PersonMapper.selectPersonById:0:2147483647:select * from t_person where id = ? and last_name = ? and sex = ?:1:emma:0:test
publicCacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); 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 = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
從查詢的原始碼可以看出,mybatis在查詢前,先看是否需要清除快取,再通過CacheKey從快取中查詢是否有對應的key,有則返回物件,無則從資料庫中查詢,再看一級快取的作用域範圍,是STATEMENT則清除快取,否則將結果集放到快取中。
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; }
一級快取演示& & 失效情況
同一次會話期間只要查詢過的資料都會儲存在當前SqlSession的一個Map中
一級快取失效的四種情況
– 1、不同的SqlSession對應不同的一級快取
– 2、同一個SqlSession但是查詢條件不同
– 3、同一個SqlSession兩次查詢期間執行了任何一次增刪改操作
– 4、同一個SqlSession兩次查詢期間手動清空了快取
注意:
– sql標籤的flushCache屬性查詢預設flushCache=false;增刪改insert|update|delete會修改資料,預設flushCache=true,會同時清空一級和二級快取。
– sqlSession.clearCache():只是用來清除一級快取,不會清除二級快取。
– 當在某一個作用域 (一級快取Session/二級快取Namespaces) 進行了 C/U/D 操作後,預設該作用域下所有有select中的快取將被clear。
二級快取