在进行数据库查询之前,MyBatis首先会检查以及缓存中是否有相应的记录,若有的话直接返回即可。一级缓存是数据库的最后一道防护,若一级缓存未命中,查询请求将落到数据库上。
一级缓存是在 BaseExecutor 被初始化的
| 12
 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),以及提交和回滚事务时被清空。
一级缓存的逻辑
| 12
 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
| 12
 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查询数据库,并将查询结果写入缓存中。
| 12
 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;
 }
 
 | 
一级缓存的逻辑比较简单