查询语句对应的方法比较多,有如下几种:

  • executeWithResultHandler
  • executeForMany
  • executeForMap
  • executeForCursor

这些方法在内部调用了 SqlSession 中的一些 select*方法,比如 selectList、selectMap、
selectCursor 等。这些方法的返回值类型是不同的,因此对于每种返回类型,需要有专门的处
理方法。

以selectList 方法为例,该方法的返回值类型为 List。但如果我们的 Mapper 或 Dao
的接口方法返回值类型为数组,或者 Set,直接将 List 类型的结果返回给 Mapper/Dao 就不合适了。

execute_等方法只是对 select _等方法做了一层简单的封装,因此接下来我们应们应该
把目光放在这些 select * 方法上。

selectOne ⽅法分析

selectOne 在内部会调用selectList方法,selectOne和selectList方法是有联系的,同时分析selectOne 方法等同于分析selectList 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// -☆- DefaultSqlSession
public <T> T selectOne(String statement, Object parameter) {
// 调用 selectList 获取结果
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
// 返回结果
return list.get(0);
} else if (list.size() > 1) {
// 如果查询结果大于 1 则抛出异常,这个异常也是很常见的
throw new TooManyResultsException("……");
} else {
return null;
}
}

// -☆- DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter) {
// 调用重载方法
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

来看看 selectList 方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final Executor executor;

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用 Executor 实现类中的 query 方法
return executor.query(ms, wrapCollection(parameter),
rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("……");
} finally {
ErrorContext.instance().reset();
}
}

executor 变量,该变量类型为 Executor。Executor 是一个接口,它的实现类如下:

image.png
默认情况下,executor 的类型为CachingExecutor,该类是一个装饰器类,用于给目标 Executor 增加二级缓存功能。目标Executor默认情况下是 SimpleExecutor

接下来继续分析 selectOne 方法的调用栈。先来看看 CachingExecutor 的 query 方法是怎样实现的。

1
2
3
4
5
6
7
8
9
10
11
// -☆- CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建 CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用重载方法
return query(ms, parameterObject,
rowBounds, resultHandler, key, boundSql);
}

上面的代码用于获取 BoundSql 对象,创建 CacheKey 对象,然后再将这两个对象传给重
载方法。

重载方法

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
// -☆- CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key,
BoundSql boundSql) throws SQLException {
// 从 MappedStatement 中获取缓存
Cache cache = ms.getCache();
// 若映射文件中未配置缓存或参照缓存,此时 cache = null
if (cache != null) {
flushCacheIfRequired(ms);

if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 若缓存未命中,则调用被装饰类的 query 方法
list = delegate.<E>query(ms, parameterObject,
rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用被装饰类的 query 方法
return delegate.<E>query(
ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

以上代码涉及到了二级缓存,若二级缓存为空,或未命中,则调用被装饰类的 query 方
法。

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
// -☆- BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key,
BoundSql boundSql) throws SQLException {
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();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope()==LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}

主要用于从一级缓存中查找查询结果,若缓存未命中,再向数据库进行查询。DeferredLoad,这个类用于延迟加载。

queryFromDatabase 方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// -☆- BaseExecutor
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 {
// 调用 doQuery 进行查询
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;
}

最终还会调用 doQuery 进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// -☆- SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询操作
return handler.<E>query(stmt, resultHandler);
} finally {
// 关闭 Statement
closeStatement(stmt);
}
}

以 PreparedStatementHandler 为例,看看它的 query 方
法是怎样实现的。如下:

1
2
3
4
5
6
7
8
9
// -☆- PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行 SQL
ps.execute();
// 处理执行结果
return resultSetHandler.<E>handleResultSets(ps);
}

整个调用过程总算要结束了。查询过程涉及到了很多方法调用,不把这些调用方法搞清楚,很难对MyBatis 的查询过程有深入的理解。所以在接下来的章节中,我将会对一些重要的调用进行分析。