MyBatis的SQL执行过程
在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了
那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:
- 《SQL执行过程(一)之Executor》
- 《SQL执行过程(二)之StatementHandler》
- 《SQL执行过程(三)之ResultSetHandler》
- 《SQL执行过程(四)之延迟加载》
MyBatis中SQL执行的整体过程如下图所示:
在 SqlSession 中,会将执行 SQL 的过程交由Executor
执行器去执行,过程大致如下:
- 通过
DefaultSqlSessionFactory
创建与数据库交互的SqlSession
“会话”,其内部会创建一个Executor
执行器对象 - 然后
Executor
执行器通过StatementHandler
创建对应的java.sql.Statement
对象,并通过ParameterHandler
设置参数,然后执行数据库相关操作 - 如果是数据库更新操作,则可能需要通过
KeyGenerator
先设置自增键,然后返回受影响的行数 - 如果是数据库查询操作,则需要将数据库返回的
ResultSet
结果集对象包装成ResultSetWrapper
,然后通过DefaultResultSetHandler
对结果集进行映射,最后返回 Java 对象
上面还涉及到一级缓存、二级缓存和延迟加载等其他处理过程
SQL执行过程(三)之ResultSetHandler
高能预警 ❗ ❗ ❗ ❗ ❗ ❗
DefaultResultSetHandler(结果集处理器)将数据库查询结果转换成 Java 对象是一个非常繁琐的过程,需要处理各种场景,如果继续往下看,请做好心理准备😄😄
可以先跳转到 DefaultResultSetHandler,查看流程图
在前面SQL执行过程一系列的文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,然后通过StatementHandler去执行数据库相关操作,并获取到数据库的执行结果
如果是数据库查询操作,则需要通过ResultSetHandler
对查询返回的结果集进行映射处理,转换成对应的Java对象,算是SQL执行过程的最后一步,那么我们来看看MyBatis是如何完成这个繁杂的解析过程的
ResultSetHandler接口的实现类如下图所示:
先回顾一下ResultSetHandler
在哪被调用,在PreparedStatementHandler
的query
方法中,代码如下:
- 属性
resultSetHandler
默认为DefaultResultSetHandler
对象,可以回到 《SQL执行过程(二)之StatementHandler》的BaseStatementHandler小节中的构造方法
的第3
步可以看到 - 调用
resultSetHandler
的handleResultSets(Statement stmt)
方法,对结果集进行映射,转换成Java对象并返回
ResultSetWrapper
因为在
DefaultResultSetHandler
中,对ResultSet
的操作更多的是它的ResultSetWrapper
包装类,所以我们先来看看这个类
org.apache.ibatis.executor.resultset.ResultSetWrapper
:java.sql.ResultSet
的包装类,为DefaultResultSetHandler
提供许多便捷的方法,直接来看它的代码
构造方法
resultSet
:被包装的ResultSet
结果集对象typeHandlerRegistry
:类型处理器注册表,因为需要进行Java Type与Jdbc Type之间的转换columnNames
:结果集中的所有列名classNames
:结果集中的每列的对应的Java Type的名称jdbcTypes
:结果集中的每列对应的Jdbc TypetypeHandlerMap
:结果集中每列对应的类型处理器mappedColumnNamesMap
:保存每个ResultMap对象中映射的列名集合,也就是我们在<resultMap />
标签下的子标签配置的column
属性unMappedColumnNamesMap
:保存每个ResultMap对象中未映射的列名集合,也就是没有在<resultMap />
标签下配置过,但是查询结果返回了
在构造方法中,会初始化上面的columnNames
、classNames
和jdbcTypes
属性
getTypeHandler方法
getTypeHandler(Class<?> propertyType, String columnName)
:通过列名和Java Type获取对应的TypeHandler
类型处理器,方法如下:
大致逻辑如下:
- 先从
Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap
属性中获取类型处理器 - 如果从缓存中没有获取到,则尝试根据Jdbc Type和Java Type从
typeHandlerRegistry
注册表获取 - 如果还是没有获取到,则根据
classNames
中拿到结果集中该列的Java Type,然后在从typeHandlerRegistry
注册表获取 - 还是没有获取到,则设置为
ObjectTypeHandler
- 最后将其放入
typeHandlerMap
缓存中
loadMappedAndUnmappedColumnNames方法
loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)
方法,初始化mappedColumnNamesMap
和unMappedColumnNamesMap
两个属性,分别为映射的列名和未被映射的列名,方法如下:
- 获取配置的列名的前缀,全部大写,通常是没有配置的
- 获取
ResultMap
中配置的所有列名,并添加前缀
如果在<select />
上面配置的是resultType
属性,则返回的是空集合,因为它创建的ResultMap
对象中只有Java Type属性 - 遍历结果集中所有的列名,如果在
<resultMap />
标签中的子标签配置的column
属性有包含这个列名,则属于映射的列名 - 否则就属于未被映射的列名
ResultSetHandler
org.apache.ibatis.executor.resultset.ResultSetHandler
:结果集映射接口,代码如下:
DefaultResultSetHandler
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
:实现ResultSetHandler接口,处理数据库的查询结果,对结果集进行映射,将结果转换成Java对象
由于该类嵌套的方法太多了,可能一个方法会有十几层的嵌套,所以本分不会进行全面的分析
因为我查看这个类的时候是从下面的方法一层一层往上看的,注释我全部添加了,所以可以参考我的注释一步一步查看
接下来的描述可能有点混乱,请按照我在方法前面表明的顺序进行查看,参考: DefaultResultSetHandler.java
先来看下DefaultResultSetHandler
处理结果集的方法的流程图:
构造方法
- 上面的属性有点多,可以先根据注释进行理解,也可以在接下来的方法中逐步理解
1.handleResultSets方法
handleResultSets(Statement stmt)
方法,处理结果集的入口
multipleResults
用于保存映射结果集得到的结果队形,多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象,而实际上,每个 Object 是List<Object>
对象- 获取
ResultSet
对象,并封装成ResultSetWrapper
- 获得当前 MappedStatement 对象中的
ResultMap
集合,XML 映射文件中<resultMap />
标签生成的,或者 配置 "resultType" 属性也会生成对应的ResultMap
对象
在<select />
标签配置ResultMap
属性时,可以以逗号分隔配置多个,如果返回多个 ResultSet 则会一一映射,通常配置一个 - 如果有返回结果,但是没有
ResultMap
接收对象则抛出异常 - 调用
handleResultSet
方法,完成结果集的映射,全部转换的 Java 对象,保存至multipleResults
集合中,或者this.resultHandler
中(用户自定的,通常不会) - 获取 resultSets 多结果集属性的配置,存储过程中使用,暂时忽略,本文暂不分析
完成结果集映射的任务还是交给了2.handleResultSet方法
2.handleResultSet方法
handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)
方法,处理结果集
- 暂时忽略,因为只有存储过程的情况时 parentMapping 为非空,查看上面的1.handleResultSets方法的第
6
步 - 用户没有指定
ResultHandler
结果处理器
- 创建
DefaultResultHandler
默认结果处理器,就是使用一个List集合保存转换后的Java对象 - 调用
handleRowValues
方法,处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中 - 将结果集合添加至
multipleResults
中
- 用户指定了自定义的
ResultHandler
结果处理器,和第2
步的区别在于,处理后的Java对象不会保存在multipleResults
中,仅保存在ResultHandler
中,用户可通过它获取 - 关闭 ResultSet 结果集对象
通常我们不会自定义结果处理器的,所以第4
步本文暂不分析,我们来看到第2
步,最终还是交给了3.handleRowValues方法
3.handleRowValues方法
handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
方法,处理结果集
- 如果当前 ResultMap 存在内嵌的 ResultMap
例如<resultMap />
标签中<association />
或者<collection />
都会创建对应的 ResultMap 对象,该对象的 id 会设置到ResultMapping
的nestedResultMapId
属性中,这就属于内嵌的 ResultMap
- 如果不允许在嵌套语句中使用分页,则对 rowBounds 进行校验,设置了 limit 或者 offset 则抛出异常,默认允许
- 校验要不要使用自定义的 ResultHandler,针对内嵌的 ResultMap
- 处理结果集,进行映射,生成返回结果,保存至
resultHandler
或者设置到parentMapping
(存储过程相关,本文暂不分析)的对应属性中,这里会对内嵌的 ResultMap 进行处理,调用handleRowValuesForNestedResultMap
方法
- 处理结果集,进行映射,生成返回结果,保存至
resultHandler
或者设置到parentMapping
(存储过程相关,本文暂不分析)的对应属性中,调用handleRowValuesForSimpleResultMap
方法
这里先来看到第2
步中的4.handleRowValuesForSimpleResultMap方法,因为这个处理的情况相比第1
步调用的方法简单些
4.handleRowValuesForSimpleResultMap方法
handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
方法,处理结果集(不含嵌套映射)
这里创建了一个DefaultResultContext
保存结果的上下文对象,点击去你会发现有3个属性:
resultObject
:暂存映射后的返回结果,因为结果集中可能有很多条数据resultCount
:记录经过DefaultResultContext
暂存的对象个数stopped
:控制是否还进行映射
- 根据
RowBounds
中的offset
跳到到结果集中指定的记录 - 检测已经处理的行数是否已经达到上限(
RowBounds.limit
)以及ResultSet
中是否还有可处理的记录 - 调用
resolveDiscriminatedResultMap
方法,获取最终的 ResultMap
因为 ResultMap 可能使用到了<discriminator />
标签,需要根据不同的值映射不同的 ResultMap
如果存在Discriminator
鉴别器,则根据当前记录选择对应的 ResultMap,会一直嵌套处理 - 调用
getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
方法,从结果集中获取到返回结果对象,进行映射,比较复杂,关键方法!!! - 调用
storeObject
方法,将返回结果对象保存至resultHandler
,或者设置到父对象parentMapping
(存储过程相关,本文暂不分析)的对应属性中
对于第3
、4
、5
步的三个方法,我们一个一个来看
- 4.1resolveDiscriminatedResultMap方法
- 4.2getRowValue方法
- 4.3storeObject方法
4.1resolveDiscriminatedResultMap方法
resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
方法,如果存在<discriminator />
鉴别器,则进行处理,选择对应的 ResultMap
,会一直嵌套处理
- 获取 ResultMap 中的
Discriminator
鉴别器,<discriminator />
标签会被解析成该对象 - 调用
getDiscriminatorValue
方法,获取当前记录中该列的值,通过类型处理器转换成了对应的类型,方法如下:
Discriminator
鉴别器根据该值获取到对应的 ResultMap 的 id
- 存在对应的 ResultMap 对象,则获取到
- 记录上一次的鉴别器
- 获取到对应 ResultMap 内的鉴别器,可能鉴别器里面还有鉴别器
- 检测是否出现循环嵌套了
Discriminator
鉴别结果没有对应的 ResultMap,则直接跳过- 返回最终使用的 ResultMap 对象
4.2getRowValue方法
getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
方法,处理结果集
- 创建一个保存延迟加载的集合
ResultLoaderMap
对象lazyLoader
,如果存在代理对象,创建的代理对象则需要通过它来执行需要延迟加载的方法,在后续会将到😈 - 调用
createResultObject
方法,创建返回结果的实例对象rowValue
(如果存在嵌套子查询且是延迟加载则为其创建代理对象,后续的延迟加载保存至lazyLoader
中即可) - 如果上面创建的返回结果的实例对象
rowValue
不为 null,并且没有对应的 TypeHandler 类型处理器,则需要对它进行赋值
例如我们返回结果为 java.lang.String 就不用了,因为上面已经处理且赋值了
- 将返回结果的实例对象封装成 MetaObject 对象
metaObject
,便于操作 - 标记是否成功映射了任意一个属性,
useConstructorMappings
表示是否在构造方法中使用了参数映射 - 调用
shouldApplyAutomaticMappings
方法,检测是否需要自动映射,就是对未被映射的列进行处理 - 调用
applyAutomaticMappings
方法,从结果集中将未被映射的列值设置到返回结果metaObject
中,返回是否映射成功(设置了1个或以上的属性值) - 调用
applyPropertyMappings
方法,从结果集中将 ResultMap 中需要映射的列值设置到返回结果metaObject
中,返回是否映射成功(设置了1个或以上的属性值) - 如果没有成功映射任意一个属性,则根据
returnInstanceForEmptyRow
全局配置(默认为false)返回空对象还是 null
- 返回该结果对象
rowValue
我们逐步来看上面的第2
、3.3
、3.4
、3.5
所调用的方法
- 4.2.1createResultObject方法
- 4.2.2shouldApplyAutomaticMappings方法
- 4.2.3applyAutomaticMappings方法
- 4.2.4applyPropertyMappings方法
4.2.1createResultObject方法
createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix)
方法,创建返回结果的实例对象(如果存在嵌套子查询且是延迟加载则为其创建代理对象)
- 记录构造方法的入参类型
- 记录构造方法的参数值
- 调用
createResultObject
方法(重载),创建返回结果的实例对象,该步骤的核心!!! - 如果返回结果的实例对象不为空,且返回结果没有对应的 TypeHandler 类型处理器,例如一个实体类,则遍历所有的映射列,如果存在嵌套子查询并且要求延迟加载,那么为该返回结果的实例对象创建一个动态代理对象(Javassist)
这样一来可以后续将需要延迟加载的属性放入lazyLoader
中即可,在后续会讲到😈 - 记录是否使用有参构造方法创建的该返回结果实例对象,就是使用了映射,后续判断返回空对象还是null需要用到
- 返回实例对象,也可能是它的动态代理对象
这里我们需要来看到第3
步调用的createResultObject
重载方法
- 4.2.1.1createResultObject重载方法
4.2.1.1createResultObject重载方法
createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
方法,找到构造方法,创建一个实例对象
创建结果对象,依次分为下面4种场景:
- 结果集只有一列,且存在对应的 TypeHandler 类型处理器,例如返回 java.lang.String
则调用createPrimitiveResultObject
方法,将该列转换成对应 Java Type 的值,然后返回 <resultMap />
标签下配置的<constructor />
标签下的构造函数参数信息不为空
则调用createParameterizedResultObject
方法,根据<constructor /
标签下的构造方法入参配置,尝试从结果集中获取入参值,并创建返回结果的实例对象- 返回类型为接口,或者有默认的构造方法
则通过实例工厂objectFactory
,使用默认无参构造方法创建返回结果的实例对象 - 找到合适的构造方法
则调用createByConstructorSignature
方法,找到合适的构造方法并创建返回结果对象
好的,接下来我们又要看到第1
、2
、4
步调用的三个方法了
- 4.2.1.2createPrimitiveResultObject方法
- 4.2.1.3createParameterizedResultObject方法
- 4.2.1.4createByConstructorSignature方法
4.2.1.2createPrimitiveResultObject方法
createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
方法,创建返回结果实例对象(通常是Java定义的类型,例如java.lang.String)
- 通过
ResultSetWrapper
根据Java Type和columnName找到对应的TypeHandler
类型处理器 - 通过
TypeHandler
类型处理器,将结果集中的结果转换成对应的 Java 对象
4.2.1.3createParameterizedResultObject方法
createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
方法
根据 <resultMap />
标签下的 <constructor />
标签配置的参数构建一个实例对象
- 需要先从结果集中获取每个
<constructor />
标签配置的参数对应的值,这里又可能存在以下三种情况:
- 该参数存在嵌套查询,则调用
getNestedQueryConstructorValue
方法,获取到该属性值 - 存在嵌套 ResultMap,则调用
getRowValue
方法,从该结果集中获取到嵌套 ResultMap 对应的值,回到了4.2getRowValue方法 - 正常情况,通过TypeHandler类型处理器,根据列名从结果集中获取到该属性值
- 通过
objectFactory
实例工厂,根据上面配置的入参信息构建一个实例对象
这里我们又要进入第1.1
步的方法
- 4.2.1.3.1getNestedQueryConstructorValue方法
4.2.1.3.1getNestedQueryConstructorValue方法
getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
方法,处理构造方法的入参出现嵌套子查询这种情况,获取该参数值
- 获得嵌套查询关联的 id
- 获取嵌套查询对应的 MappedStatement 对象
- 获取嵌套查询的参数类型
- 获取嵌套查询的参数对象,已完成初始化,调用
prepareParameterForNestedQuery
方法,进去后发现又得两层方法🙂,这里就不再展开了,比较简单,可以先参考的我的注释查看,在后续还会调用该方法,再进行解析 - 执行查询,因为这里的构造方法中的入参,所以无需判断延迟加载,在后面设置属性时就不一样了
- 获取嵌套查询中的 SQL 对象
- 获取CacheKey对象
- 创建 ResultLoader 对象
- 加载结果
- 返回子查询返回的值
4.2.1.4createByConstructorSignature方法
createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
方法,尝试找一个合适的构造方法构建一个实例对象
- 获取所有的构造函数
- 找到添加了
@AutomapConstructor
注解的构造方法,如果存在则调用createUsingConstructor
方法,创建一个实例对象 - 否则,遍历所有的构造方法
- 如果构造方法的入参与结果集中列的个数相同,并且入参的 Java Type 和列的 Jdbc Type 有类型处理器
- 使用这个构造方法创建返回结果的实例对象,调用
createUsingConstructor
方法,创建一个实例对象
上面需要调用的createUsingConstructor
方法比较简单,这里就不再展开了,大致逻辑就是从结果集中获取到该构造方法所有的入参,然后构建一个实例对象
4.2.2shouldApplyAutomaticMappings方法
shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested)
方法,检测是否需要自动映射(对未被映射的列进行处理)
- 如果
<resultMap />
中的autoMapping
配置不为空,则返回该配置 - 否则通过全局配置来判断,默认
PARTIAL
,也就是不是嵌套映射则需要对未被映射的列进行处理,嵌套查询的话不会对未被映射的列进行处理(需要配置为FULL
)
4.2.3applyAutomaticMappings方法
applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)
方法,对未被映射的字段进行映射
- 调用
createAutomaticMappings
方法,将这些未被映射的字段创建对应的UnMappedColumnAutoMapping
对象(包含列名、属性名、类型处理器、是否为原始类型) - 遍历未被映射的字段数组,将这些属性设置到返回结果对象中
- 通过 TypeHandler 类型处理器获取未被映射的字段的值
- 如果属性值不为空,或者配置了值为 null 也往返回结果设置该属性值(不能是基本类型),则往返回结果中设置该属性值
这里我们来看到createAutomaticMappings
方法
- 4.2.3.1createAutomaticMappings方法
4.2.3.1createAutomaticMappings方法
createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)
方法,将这些未被映射的字段创建对应的 UnMappedColumnAutoMapping 对象
- ResultMap 中需要 "自动映射" 的列会缓存起来,这是对应的缓存 key
- 先从
autoMappingsCache
缓存中获取该 ResultMap 对应的UnMappedColumnAutoMapping
集合autoMapping
,没有的话才进行接下来的解析 - 获取未映射的的列名集合,也就是数据库返回的列名在 ResultMap 中没有配置,例如我们配置的是 resultType 属性就全部没有配置,然后进行遍历
- 如果配置了前缀,则将列名中的前缀去掉作为属性名
- 根据列名从入参对象中获取对应的属性名称,不管大小写都可以找到
- 开始为该属性创建
UnMappedColumnAutoMapping
对象,如果返回对象中有该属性的 setter 方法
- 获取属性名称的 Class 对象
- 如果有对应的 TypeHandler 类型处理器,创建该属性的
UnMappedColumnAutoMapping
对象,设置列名、属性名、类型处理器、是否为原始类型,添加到autoMapping
集合中 - 否则,执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为
NONE
,不做任何行为
- 该属性没有setter方法,执行发现自动映射目标为未知列(或未知属性类型)的行为,默认为
NONE
,不做任何行为 - 返回
autoMapping
,并添加到autoMappingsCache
缓存中
4.2.4applyPropertyMappings方法
applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
方法,将明确被映射的字段设置到返回结果中
- 获取 ResultMap 中明确需要进行映射的列名集合
mappedColumnNames
- 获取 ResultMap 中所有的 ResultMapping 对象,然后进行遍历
- 从结果集获取属性值设置到返回结果中,需要满足下面三个条件中的一个:
- 配置的
column
属性为{prop1:col1,prop2:col2}
这种形式,一般就是嵌套子查询,表示将col1和col2的列值设置到嵌套子查询的入参对象的prop1和prop2属性中 - 基本类型的属性映射
- 多结果集的场景处理,该属性来自另一个结果集,存储过程相关,本文暂不分析
- 完成映射,调用
getPropertyMappingValue
方法,从结果集中获取到对应的属性值value
- 没有配置对应的 Java 属性则跳过
- 如果是
DEFERRED
占位符(延迟加载),则跳过 - 将属性值设置到返回结果中
我们来看看getPropertyMappingValue
方法
- 4.2.4.1getPropertyMappingValue方法
4.2.4.1getPropertyMappingValue方法
getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
方法
完成映射,从结果集中获取到对应的属性值
可以看到该ResultMapping
属性配置可能有三种情况:
- 如果是嵌套子查询,则调用
getNestedQueryMappingValue
方法,执行嵌套子查询,返回查询结果,如果需要延迟记载则返回的是DEFERRED
- 如果是多结果集,存储过程相关,则调用
addPendingChildRelation
方法, 多结果集处理,延迟加载,返回占位符,本文暂不分析 - 正常情况,获取 TypeHandler 类型处理器,通过它从结果集中获取该列对应的属性值
我们这里继续看到第1
步中的调用的方法
- 4.2.4.2getNestedQueryMappingValue方法
4.2.4.2getNestedQueryMappingValue方法
getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
方法
执行嵌套子查询,返回查询结果,如果需要延迟记载则返回的是 DEFERRED
- 获取嵌套子查询关联的ID、属性名、嵌套子查询的
MappedStatement
对象、嵌套子查询的参数类型 - 从结果集中获取参数值,准备好嵌套子查询的入参,调用
prepareParameterForNestedQuery
方法 - 获得嵌套子查询的 BoundSql 对象
- 获得嵌套子查询本次查询的 CacheKey 对象、嵌套子查询的返回 Java Type
- 检查缓存中已存在本次子查询的数据,已存在的话则进行下面两步
- 则创建
DeferredLoad
对象,并通过该DeferredLoad
对象从缓存中加载结果对象
这也算延迟加载,嵌套子查询的结果在缓存中,然后会在查询接口后进行加载,可以回到 《SQL执行过程(一)之Executor》的BaseExecutor小节中的query
方法的第6
步看看 - 返回
DEFERRED
延迟加载默认对象
- 缓存中不存在本次子查询的数据
- 创建
ResultLoader
对象resultLoader
- 如何该属性还要求了是延迟加载
- 则将其添加到
ResultLoader.loaderMap
中,等待真正使用时再执行嵌套查询并得到结果对象
可以回到4.2.1createResultObject方法的第4
步看一下,如果存在嵌套子查询并且要求延迟加载,那么为该返回结果的实例对象创建一个动态代理对象(Javassist),后续将需要延迟加载的属性放入lazyLoader
(就是上面的ResultLoader)中即可 - 返回
DEFERRED
延迟加载默认对象
- 否在直接加载
resultLoader
对象,获取到该属性值
- 最后返回该属性值或者
DEFERRED
延迟加载默认对象
这里我们再来看到第2
步中调用的方法
- 4.2.4.2.1prepareParameterForNestedQuery方法
4.2.4.2.1prepareParameterForNestedQuery方法
prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)
方法,为嵌套子查询准备好入参
- 嵌套子查询是有多个属性映射,则调用
prepareCompositeKeyParameter
方法,从结果集中获取多个属性值设置到入参对象中
配置的column
属性为{prop1:col1,prop2:col2}
这种形式,表示将col1和col2的列值设置到嵌套子查询的入参对象的prop1和prop2属性中 - 只有一个属性映射,则调用
prepareSimpleKeyParameter
方法,从结果集中直接获取嵌套查询的入参
来看看第1
、2
步调用的方法
- 4.2.4.2.2prepareCompositeKeyParameter方法
- 4.2.4.2.3prepareSimpleKeyParameter方法
4.2.4.2.2prepareCompositeKeyParameter方法
prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)
方法
处理嵌套子查询有多个属性映射作为入参的场景,获取到多个属性值到子查询的入参中
- 创建一个嵌套子查询的入参的实例对象
parameterObject
,调用instantiateParameterObject
方法,很简单,点击去看一下就知道了 - 开始遍历ResultMapping中的
List<ResultMapping> composites
组合字段 - 获取嵌套子查询的入参该属性对应的 TypeHandler 处理器
- 通过 TypeHandler 根据该属性的
column
列名从该结果集中获取值 - 设置属性值到子查询的入参对象中
- 返回
parameterObject
子查询入参对象
4.2.4.2.3prepareSimpleKeyParameter方法
prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)
方法,从结果集中直接获取嵌套查询的入参
- 根据Java Type获取到 TypeHandler 类型处理器
- 根据TypeHandler从结果集中将该列对应的值转化成入参
4.3storeObject方法
storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs)
方法
将返回结果 对象保存至 resultHandler
,或者设置到父对象 parentMapping
(存储过程相关,本文暂不分析)的对应属性中
- 如果
parentMapping
不为空,则调用linkToParents
方法,嵌套查询或嵌套映射,将返回结果设置到父对象的对应属性中,存储过程相关,本文暂不分析 - 调用
callResultHandler
方法,普通映射,将结果对象保存到 ResultHandler 中
来看到第2
步调用的方法
4.3.1callResultHandler方法
callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue)
方法
普通映射,将结果对象保存到 ResultHandler 中
- 在
resultContext
上下文对象对象中暂存返回结果rowValue
,并递增返回结果的数量,可以回到4.handleRowValuesForSimpleResultMap方法看一下 - 通过
resultHandler
对resultContext
上下文对象暂存的返回结果进行处理,在DefaultResultHandler
中你可以看到
就是往List集合中添加返回结果
结束语
回顾到3.handleRowValues方法中,上面已经对结果集(不含嵌套ResultMap)进行映射的整个过程进行了分析,在defaultResultHandler
和multipleResults
都可以获取到映射后的结果
本来想继续分析结果集(含嵌套ResultMap)这种情况的,调用的是handleRowValuesForNestedResultMap
方法,由于上面已经嵌套太多层方法了,就不再分析第二种更复杂的情况,本文本身就不好编排,再进行嵌套就无法阅读了😢,可以参考 DefaultResultSetHandler.java根据注释,进行理解
其中涉及到的DefaultResultContext
和DefaultResultHandler
都比较简单,也不列出来了,自行根据注释查看一下
总结
本文分析了DefaultResultSetHandler
是如何处理结果集,进行映射,转换成Java对象的,总的来说就是根据ResultMap对象,对于不同的场景进行处理分析,映射成我们需要的Java对象,需要考虑的情况太多,所以这个类比较复杂,这里也仅对结果集(不含嵌套ResultMap)进行映射的整个过程进行了分析,关于含嵌套ResultMap的结果集来说,可能更加稍微复杂一点,不过也相差无几,可以参考 DefaultResultSetHandler.java
到这里SQL执行过程算是结束了,但是其中还有一部分延迟加载的内容没有分析,本来准备在这篇文档中分析的,发现已经写太多内容了,所以放在下一篇文档中