本系列文章皆在从细节 着手,由浅入深的分析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
更加清晰和一致。