2020,你好
每次当我从sqlsession工厂获取一个sqlsession时,都会创建一个BaseExecutor执行器,而一级缓存跟随执行器一起创建
///DefaultSqlSessionFactory 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); 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); return executor; } //BaseExecutor protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>(); //一级缓存 this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; } 复制代码
也就说一个会话配一个一级缓存。 一级缓存的作用范围是sqlsesion, 其生命周期也跟随sqlsession生命周期
一级缓存用PerpetualCache类表示,PerpetualCache 实现了Cache接口。而Cache 接口是Mybatis定义的缓存接口。PerpetualCache只是众多Cache 实现中的一种。
public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } } 复制代码
可以看出,PerpetualCache 的本质就是HashMap。
首先得讲讲缓存的K值
在Mybatis中不管是一级缓存,还是二级缓存,都使用CacheKey 类对象作为K值。
CacheKey 值的相等判断,作为命中的决定因素就显得尤为重要了。
看CacheKey的创建
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { CacheKey cacheKey = new CacheKey(); //1.statementId cacheKey.update(ms.getId()); //2. rowBounds.offset cacheKey.update(rowBounds.getOffset()); //3. rowBounds.limit cacheKey.update(rowBounds.getLimit()); //4. SQL语句 cacheKey.update(boundSql.getSql()); //5. 将每一个要传递给JDBC的参数值也更新到CacheKey中 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); 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); } } //6. 环境id if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } //CacheKey hashcode计算 public void update(Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); } 复制代码
从其创建可以看出CacheKey的决定条件:
每个条件都会被计算到hashcode 的值中。
默认是开启的
当我们使用sqlsession 执行查询相关操作时, 最终会执行BaseExecutor.query。此时一级缓存开始工作
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); //创建key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 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; } //从数据库中查询,并把结果缓存到一级缓存中, private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } //查询结果缓存到一级缓存中 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } 复制代码
流程总结:
一级缓存比较简单,没有所谓的过期清除,更新操作。这样意味着,我们在使用一级缓存时,要把一级缓存可能造成的数据不一致情况考虑进来。
一级缓存内数据存活期比较短暂。
清空时机:
1.同一个sqlsesion执行更新操作时,
public int update(MappedStatement ms, Object parameter) throws SQLException { //清空一级缓存。 clearLocalCache(); return doUpdate(ms, parameter); } public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } 复制代码
2.当我在代码中主动调用sqlsesion#clearCache方法清空
public void clearCache() { executor.clearLocalCache(); } 复制代码
3.当调用sqlseesion#close方法时, 此时直接将 一级缓存对象释放了。
public void close() { try { executor.close(isCommitOrRollbackRequired(false)); closeCursors(); dirty = false; } finally { ErrorContext.instance().reset(); } } public void close(boolean forceRollback) { try { ... } catch (SQLException e) { } finally { transaction = null; deferredLoads = null; localCache = null;//直接把localCache置空 localOutputParameterCache = null; closed = true; } } 复制代码
4.sqlsession 被回收,BaseExecutor ,一级缓存一并回收
如果我们不想启用一级缓存怎么弄?
禁用一级缓存的奥秘就在BaseExecutor#query 方法中这两行中。
if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } ... if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } 复制代码
1.SQL级别
ms.isFlushCacheRequired==true 对应的配置文件中
<select id="queryTrastatusDateMax" resultType="String" flushCache="true"> 复制代码
2.全局级别
configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT 对应配置文件中
<setting name="localCacheScope" value="STATEMENT" /> 复制代码
一级缓存相比与二级缓存比较简单。
总结起来: