在进行数据库查询之前,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;
}

一级缓存的逻辑比较简单