本系列文章皆在从细节 着手,由浅入深的分析Mybatis
框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis
的工作原理。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
在之前Mybatis流程分析(八): 从JDBC出发,透彻分析Mybatis执行sql的原理中,我们从原生JDBC
入手,分析了Mybaits
内部执行sql
语句的秘密。本质来看Mybatis
中sql
执行的底层逻辑就是将sql
处理逻辑委托给statement
对象,
进一步,当sql
语句执行完毕后,下一步要做的就是对结果集进行处理。此时Mybatis
内部又会将处理逻辑委托结果集处理器的ResultSetHandler
来完成。
接下来,我们便来看看Mybatis
内部在处理结果集
时与我们之前JDBC
操纵ResultSet
有何区别。
JDBC
操纵结果集
与之前分析一样,在分析Mybaits
内部是如何来对结果集
进行处理之前,我们先来看看原生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");
// 处理封装数据
}
}
}
可以看到,上述代码的流程大致可以总结为如下的流程:
1. 创建并执行SQL查询:
- 使用
Connection
建立数据库连接。 - 创建
Statement
或PreparedStatement
对象并执行 SQL 查询。 - 获取
ResultSet
对象来存储查询结果。
2. 遍历和操作结果集:
- 使用
ResultSet
对象的next()
方法遍历结果集中的每一行数据。 - 使用
getXXX()
方法从结果集中获取特定列的值,其中XXX
表示数据类型。
总之,操作 ResultSet
的主要步骤是创建查询、遍历结果集 。而在遍历结果集时,使用 getXXX()
方法获取列的值,其中 XXX
根据列的数据类型进行选择,然后对数据进行相应的操作。此外,确保在完成后关闭所有相关资源以避免资源泄漏。
此时,不妨考虑这样一个问题。即如何让操纵结果集的流程变得更加通用
。那何为通用呢?就是说只暴露给用户
这样只有一个接口
,其只需传入sql
语句,并配置一个需要返回的bean
对象信息,程序就能执行sql
语句,并将sql
执行结果封装为其配置的bean
。
如果你是框架的设计者对于这一需求,你该如何来进行程序设计呢?我想你大概率会采用一个Map
结构来保存映射信息。这样,在循环处理结果集时,只需遍历对应Map
就可以获取对应列
的名称,进而也就可以获取到相应的内容了。如果你这样去想,说明你对于Mybatis
内部结果集的处理其实理解的已经理解的八九不离十啦~~~
Mybatis
内部对于结果集的处理
接下来,不妨跟着笔者的思路,让我们来看看Mybatis
内部究竟是如何来处理ResultSet
进行处理的。
但在分析之前,笔者想问大家一个问题,对于结果集
的处理流程我们该从如何看起呢?换言之,Mybatis
内部对于结果集
的处理从何处开始的呢?如果你能脱口回答出说出:当然是从 SimpleExecutor
中query
方法开始,进而进入到PreparedStatementHandler
中的query
方法啦!因为这是Mybatis
内部就是通过这样的调用逻辑来执行sql
的。
对于这样的答案,笔者将感到十分欣慰。这说明你曾认真读过笔者之前的文章,笔者真的很感谢的你的信任和认可,感谢你肯花出时间来阅读笔者的拙作🙆
。
接下来,就让我们看看PreparedStatementHandler
中的query
方法内部是如何对结果集
进行处理的。
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);
}
可以看到,在PreparedStatementHandler
中的query
方法内部其会将结果集的处理逻辑交给resultSetHandler
来完成。
事实上,ResultSetHandler
是Mybatis
内部的一个重要的组件,它负责将数据库查询的结果集(ResultSet
)转换成Java
对象或Java
集合的操作。换言之,ResultSetHandler
的主要任务是处理数据库查询结果的映射,将结果集中的数据转化为应用程序可以操作的Java
对象。
此外,在大多数情况下Mybatis
内部会自动选择合适的ResultSetHandler
实现,但最常用的还是DefaultResultSetHandler
。
结果集处理逻辑
进一步,DefaultResultSetHandler
中方法handleResultSet
的逻辑如下:
DefaultResultSetHandler # handleResultSet()
java
public List<Object> handleResultSets(Statement stmt)
throws SQLException {
// 持有最后封装的结果信息
final List<Object> multipleResults = new ArrayList<>();
// 省略其他无关代码.....
// 获取MappedStatment中存储的ResultSetMap信息
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 应对配合的ResultMap标签
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
// ..... 省略其他无关代码
}
// 主要处理标签中配置的ResultSet信息
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
// .....省略其他无关代码
handleResultSet(rsw, resultMap, null, parentMapping);
// .....省略其他无关代码
}
}
return collapseSingleResultList(multipleResults);
}
针对上述代码中出现的ResultSet
和ResutMap
,我们有必要介绍一些Mybatis
中有关ResultSet
和ResutMap
的相关知识。
事实上,在Mybatis
内部ResultSet
和ResutMap
都可用于配置返回内容,但两者也是有区别的。具体如下:
-
ResultMap :
ResultMap
用于定义单个查询结果的映射规则。你可以在sql
映射文件中定义一个或多个ResultMap
,并将它们与不同的查询语句关联。例如配置如下的ResultMap
映射信息xml<resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name" /> </resultMap>
此时,你可以在查询语句中引用这些 ResultMap
,告诉Mybatis
如何将查询结果映射到 Java
对象。例如,可以通过如下的方式使用配置的ResultMap
信息。
xml
<select id="selectUser" resultMap="userResultMap">
SELECT user_id, user_name
FROM users
WHERE user_id = #{userId}
</select>
- ResultSet : 通过可以在
sql
映射文件的<select>
元素中使用resultSets
属性来指定一个或多个结果集的名称。
此时,你可以在代码中获取这些结果集的名称,然后根据名称来处理每个结果集的映射规则,这可能涉及到不同的 ResultMap
。进一步,mappedStatement.getResultSets()
方法用于获取与 <select>
元素中的 resultSets
属性关联的结果集名称数组。因为resultSets
属性用于指定一个或多个嵌套查询的结果集的名称。例如,在以下的 sql
映射关系中:
xml
<select id="selectUsersWithOrders"
resultSets="org.example.User">
SELECT user_id, user_name
FROM users
WHERE user_id = #{userId}
</select>
mappedStatement.getResultSets()
将返回一个字符串数组 org.example.User
,其中包含了 resultSets
属性中指定的结果集名称。
总之,在进行结果
关系映射时,可以根据具体的需求选择使用 ResultMap
或 mappedStatement.getResultSets()
,甚至可以同时使用它们,以便更灵活地配置返回内容和结果集处理规则。
明白了,ResultSet
和ResutMap
在Mybatis
中的作用后,让我们继续深入到DefaultResultSetHandler
内部的handleResultSet
方法看看其是如何来完成结果集
处理的。
java
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
// ... 省略其他无关方法
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
}
// ... 省略其他无关方法
}
可以看到,在 handleResultSet
内部,其又会将对于结果集
的处理逻辑委托给方法handleRowValues
来完成。事实上,这部分逻辑是相当绕的,在此我们就不一一赘述了。如下这张图的准确的反映了handleRowValues
后续的调用逻辑。
可以看到,在getRowValue
方法内部,其会通过shouldApplyAutomaticMappings
用以判断是否配置了相关的映射规则,如果配置了则执行applyAutomaticMappings
返回配置类型对应的bean
。进一步,applyAutomaticMappings
内部的逻辑如下:
java
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 本质逻辑就是获取列名称,然后获取内容
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null
|| (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// 通过metaObject将内容信息对bean中对应属性信息进行设置
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
对于applyAutomaticMappings
方法大致逻辑如下:
-
首先,在
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
中,其首先通过mapping.typeHandler
获取结果集中指定列mapping.column
的值,这个值会被映射到Java
对象属性。(注:这部分信息可在之前介绍过的resultMap
中进行配置) -
其次,在
metaObject.setValue(mapping.property, value);
中,其通过metaObject
(元对象)将结果集中的值value
设置到Java
对象的属性mapping.property
中。这是实现自动映射的核心操作,它将数据库查询结果中的列映射到Java
对象的属性上。
总结
本文,首先以原生JDBC
对于结果集的处理为起始点,分析了原生JDBC
对于结果集
的处理操作细节。以此为基础,我们详细分析了MyBatis
内部对于结果集处理的全流程。但无论如何你应该明白,在Mybatis
内部,对于结果集的处理本质和原生JDBC
操纵结果没什么明显区别。此外,为了增强结果集处理的通用性Mybatis
内部会引入一个ResultMap
结构来存储sql
和Java实体
实体对象间的映射关系,从而增强了框架
处理结果集
时的通用性。
至此,我们对于Mybatis
内部的分析也就告一段落了,后续笔者会在写一文章将对前面内容进行一个总结和回顾,希望能帮助你更加成体系的了解Mybatis
的处理逻辑。