在进行数据库查询之前,MyBatis首先会检查以及缓存中是否有相应的记录,若有的话直接返回即可。一级缓存是数据库的最后一道防护,若一级缓存未命中,查询请求将落到数据库上。
一级缓存是在 BaseExecutor 被初始化的
1 2 3 4 5 6 7 8 9
| public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache; // 省略其他字段 protected BaseExecutor(Configuration configuration, Transaction transaction) { this.localCache = new PerpetualCache("LocalCache"); // 省略其他字段初始化方法 } }
|
一级缓存的类型为 PerpetualCache,没有被其他缓存类装饰过。
一级缓存所存储从查询结果会在MyBatis执行更新操作(INSERT/UPDATE/DELETE),以及提交和回滚事务时被清空。
一级缓存的逻辑
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
| public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); // 创建 CacheKey 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 { 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--; } return list; }
|
在访问一级缓存之前,MyBatis 首先会调用 createCacheKey 方法创建 CacheKey
createCacheKey
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 48 49 50
| public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 创建 CacheKey 对象 CacheKey cacheKey = new CacheKey(); // 将 MappedStatement 的 id 作为影响因子进行计算 cacheKey.update(ms.getId()); // RowBounds 用于分页查询,下面将它的两个字段作为影响因子进行计算 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // 获取 sql 语句,并进行计算 cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); for (ParameterMapping parameterMapping : parameterMappings) { // 非存储过程 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; // 当前大段代码用于获取 SQL 中的占位符 #{xxx} 对应的运行时参数, // 前文有类似分析,这里忽略了 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) { // 获取 Environment id 遍历,并让其参与计算 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
|
在计算 CacheKey 的过程中,有很多影响因子参与了计算。比如 MappedStatement 的
id 字段,SQL 语句,分页参数,运行时变量,Environment的id字段等。通过让这些影响因子参与计算,可以很好的区分不同查询请求。
所以,我们可以简单的把CacheKey看做是一个查询请求的id。有了CacheKey,我们就可以使用它读写缓存了。
在上面代码中,若一级缓存未命中,BaseExecutor会调用queryFromDatabase查询数据库,并将查询结果写入缓存中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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; }
|
一级缓存的逻辑比较简单