本系列文章皆在从细节 着手,由浅入深的分析Mybatis框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis的工作原理。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
在前一章Mybatis流程分析(七): 探寻Mybatis中执行sql语句的"入口"中,我们从MapperProxy中的invoke方法入手,逐一梳理invoke方法的调用逻辑,由浅入深的分析了Mybaits内部执行sql的语句的入口位置。具体来看,Mybatis中执行sql语句会委托给SqlSession中的select。进一步,sql语句的执行又将委托给Executor中的query方法。
接下来,我们便看看Executor究竟是如何来执行我们的sql语句的。但在开始分析Executor是如何执行sql之前,不妨让先来回顾一下通过原生的JDBC是如何完成数据库信息操纵的。
JDBC操纵数据库相关逻辑
java
public class JDBCDemo {
// .... 省略驱动加载
// 1. 建立数据库连接
Connection connection = DriverManager.getConnection(url, username, password);
// 2. 创建SQL语句
Statement statement = connection.createStatement();
// 3. 执行SQL查询
String selectSql = "SELECT * FROM yourtable";
ResultSet resultSet = statement.executeQuery(selectSql);
//4. 处理查询结果
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
// 处理封装数据
}
}
}
可以看到,当用原生JDBC操纵数据库时,会经历如下几个步骤:
- 建立数据库连接 :使用
DriverManager类的getConnection方法来建立与数据库的连接。你需要提供数据库的URL、用户名和密码。 - 创建SQL语句 :使用SQL语句来执行数据库操作。你可以创建
Statement、PreparedStatement或CallableStatement对象,分别用于执行不同类型的SQL语句。 - 执行SQL查询 :使用创建的
Statement或PreparedStatement对象来执行SQL查询。 - 处理查询结果 :通过
ResultSet对象来处理查询的结果数据。
接下来我们便重新将分析的重心拉回到Executor是如何来执行sql语句的上来。
Executor相关介绍
众所周知,在 MyBatis 中,执行器Executor 是一个关键的组件,它的作用是管理 SQL 语句的执行过程。此外,MyBatis 使用 Executor 来协调数据库的访问,以及将 SQL 语句映射到对应的数据库操作。
进一步,Executor 在 MyBatis 中起到了桥梁的作用,连接了应用程序和数据库之间的交互。此外,Mybatis中执行器Executor的继承关系如下所示:

为了方便后续内容的分析,此处对其中的几个常用的执行器进行一下简单的介绍:
- 简单执行器
SimpleExecutor:每执⾏⼀次update或select,就开启⼀个Statement对象,⽤完⽴刻关闭Statement对象。如果多次执⾏同⼀sql多次,则每次都进行预编译处理。 - 重⽤执⾏器
ReuseExecutor:执⾏update或select,会以sql作为key查找Statement对象,存在就使⽤,不存在就创建,⽤完后,不关闭Statement对象,⽽是放置于Map内,供下⼀次使用。即多次执⾏同⼀sql不重复进⾏预编译处理。 - 缓存执行器
CachingExecutor:其属于装饰设计模式的典型应用,具体来看,其内部会持有一个其他类型的执行器(SimpleExecutor或ReuseExecutor)。当查询数据时,其会先从缓存中获取查询结果,存在就返回,不存在再委托给持有的Executor去数据库查找。
知晓了执行器在Mybatis的类型后,接下来,我们便看看执行器内部究竟是如何来执行sql语句的。
执行逻辑分析
在上一章Mybatis流程分析(七): 探寻Mybatis中执行sql语句的"入口"中,我们曾提到过,sql执行的入口在SqlSession的select方法中。进一步,其又会将查询逻辑委托给Executor来执行,具体代码逻辑如下:
DefaultSqlSession # select
java
public void select(String statement, Object parameter,
RowBounds rowBounds, ResultHandler handler) {
<1> 根据namespce+methodId获取对应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
<2> 调用执行器中Executor中的query方法
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
// .... 省略其他无关逻辑
}
executor.query内部执行逻辑如下所示:
java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// ....省略其他无关逻辑
// 从数据库中查询相关信息
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
// ....省略其他无关逻辑
return list;
}
可以看到,在Executor中的query方法内部,会将相关的处理逻辑交给queryFromDatabase来完成。为了方便理解,此处直接贴出内部调用链相关逻辑。

事实上,如果我们不对Configuration中的Executore类型进行配置的话,Mybatis默认会构建一个SimpleExector类型的执行器。所以此处我们选取SimpleExector中的doQuery方法来进行分析。
java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
// 获取环境对象Configuration
Configuration configuration = ms.getConfiguration();
// 构建一个Statement对象信息
StatementHandler handler = configurationnewStatementHandler(wrapper, ms, parameter, rowBounds,resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler的query方法
return handler.query(stmt, resultHandler);
}
可以看到,在SimpleExecutor方法中,其并不是真正执行sql的地方。其又会将逻辑委托给StatementHandler对象来完成。相关逻辑如下:
java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// 通过对象statemnt中的exectute方法来完成sql的执行
statement.execute(sql);
// 结果集处理
return resultSetHandler.handleResultSets(statement);
}
看到此,其实开头的问题已经很清楚了。原来Mybatis中执行sql的底层逻辑和使用原生JDBC操纵的逻辑类似,其会将sql语句交给Statement来处理,当sql处理完毕后,再交由结果处理集ResultSetHandler来对结果进行处理。
解答疑问
在上一章分析中,我们曾提出这样一个问题。在DefaultSqlSession中定义的select方法其返回值为一个void,那Mybatis为什么会这样设计呢?
如下是我个人读源码的一些见解,仅供参考。通过本章分析我们知道,在Mybais中,SqlSession中的select会将处理逻辑委托给Executor中的query方法,但是query方法是有返回值的(List<T>)。
笔者猜测这样做的目的是为了与 insert、update、delete 等方法保持一致性。因为这些方法通常不会返回实体对象。这样设计的种一致性可以使 API 设计更加简单和统一。虽然 select 方法本身没有返回结果,但它仍然可以通过查询方法委托给 Executor 来获取结果。
众所周知,MyBatis 中的 Executor 负责执行 SQL 语句并返回结果。在 Executor 中的 query 方法通常具有返回值,因为查询操作通常需要返回一个结果集(ResultSet)或者一些数据对象。但是,SqlSession 并不需要直接返回查询结果,因为它的职责是提供一种高级别的 API,隐藏了底层数据库操作的细节。具体的查询结果可以由调用 SqlSession 的客户端代码来处理。
此外,设计成这种方式还有一个好处,即灵活性和可扩展性。MyBatis 允许用户在配置文件中配置不同的 Executor 实现,例如 SimpleExecutor、ReuseExecutor 或 BatchExecutor,这些实现可以根据需要执行查询,而 SqlSession 作为抽象接口不需要关心底层的具体执行细节。这种分离使得MyBatis可以灵活地适应不同的使用场景和性能需求。
总而言之,虽然 select 方法本身没有返回值,但是可以通过调用 Executor 的 query 方法来获取查询结果,然后在调用 select 方法的客户端代码中进行处理和使用这些结果。这种设计使 MyBatis 的 API 更加清晰和一致。