在文章Mybatis缓存详解中我们说了mybatis的查询流程,从sqlSession到二级缓存到一级缓存再到数据库查询 ,当我们查询时,走到SimpleExecutor进行具体数据库查询时,有如下流程
statementHandler
这里简单介绍一下statementHandler.该对象负责操作 Statement 对象与数据库进行交流,并且还会通过内部的ParameterHandler 和 ResultSetHandler 对参数进行映射,对结果进行实体类的绑定。之前我们说过executor是sqlSession背后的操作者,其实statementHandler才是executor背后的操作者。
类结构与executor类似,但我们生产中99%的场景都是使用preparedStatementHandler(默认也是preparedStatementHandler)。并且routingStatementHandler其实是一个简单工厂。根据我们配置来选初始化具体的handler
通过configuration来初始化statementHandler
使用statementHandler进行查询
ResultSetHandler只有一个子类,DefaultResultSetHandler。Mybatis的结果集处理代码全部在里面。这也是个人觉得Mybatis源码中最复杂的一块。所谓结果集处理,就是将jdbc中查询到的结果(resultSet)填充到xml中使用resultType/resultMap配置的java bean中。
如下图,我们99.99%的查询只会走下图上面部分代码。对下部分代码场景大家可以参考官网中例子。我们后续只会对上部分代码进行详细说明。 多结果集说明 核心方法为handleResultSet
我们在日常开发中几乎很少去配置resultHandler。我们可以自定义resultHandler来决定从db result到java bean的映射。(有兴趣的同学可以自行查一下)。而默认的DefaultResultHandler里面其实就一个list,默认会将解析过后的数据添加到list中。
结果集处理核心方法就是下面的handleRowValues。他的大概流程如下
1.判断是复杂结果集还是简单结果集。走各自的流程(这里着重介绍简单结果集)
2.根据结果集个数,循环处理每一行。
结果集定义
这里我们先介绍一下两种结果集的定义
- 复杂结果集
- resultMap中带有association或者collection标签并且association或者collection标签中不带有select。
复杂结果集
<resultMap id="studentMap" type="entity.StudentEntity">
<id column="id" property="id"/>
<result column="name" property="name"/>
//一对一
<association property="classRoom" resultMap="classMap"/>
<association property=" classRoomId" select="xxxx"/>
//一对多
<collection property="classRoom" select="xxx"/>
<collection property="classRoom" resultMap="classRoom"/>
</resultMap>
- 简单结果集
- resultType
- 不带association或者collection标签的resultMap
- 带有association或者collection标签的resultMap,但使用了select标签例如下面这种带有association标签。
<!-- <resultMap id="studentMap" type="entity.StudentEntity">-->
<!-- <association property="aclass" column="class_name" select="mapper.ClassMapper.queryByName"/>-->
<!-- </resultMap>-->
复杂/简单结果集判断
根据上图最后结果集处理走简单/复杂处理流程就看hasNestedResultMaps(boolean类型,默认为false)。我们查看它的构造过程。问题关键就在nestedResultMapId。如果它不为null,那hasNestedResultMaps最终就为true,整个查询就定义为复杂查询。为null,hasNestedResultMaps最终为false,整个查询就定义为简单查询
nestedResultMapId构建,可以看到是通过nestedResultMap是否为null来进行构建。
我们继续往前看nestedResultMap,我们常见的xml方式是通过XMLMapperBuilder.buildResultMappingFromContext进行构建
核心方法就是
processNestedResultMappings,代码非常简单。也跟我们上述复杂结果集定义对应上了。如果包含association或者collection或者case标签并且在这些标签中没有select语句,则定义为复杂结果集
简单结果集处理
1.跳过指定行
2.根据结果一行一行解析数据
2.1 得到该行记录对应的resultMap
2.2 通过resultMap与resultSet解析该行(重点)
2.3 保存记录
跳过指定行
得到对应的resultMap
有这样一个场景,如果我们想通过查出来的某个字段的不同值来选择不同的映射模型,比如上述的student模型中,如果班级名称为二班,则不进行班级数据的关联查询。在resultMap的配置中,mytatis为我们提供了discriminator标签来实现,跟sql中的case when用法基本一致。
所以在源码resolveDiscriminatedResultMap方法中这个步骤的目的就是通过该数据行这个字段的value来选择具体的resultMap.如果我们不使用discriminator标签(日常开发还是适用得比较少的),那这个方法返回的就是本身查询的resultMap. resultType也会封装称一个resultMap,只不过里面的各项参数都为null。
数据解析(重点)
我们已经拿到了数据行,以及数据行对应的resultMap,而resultMap也是对应一个简单/复杂的java bean,这步骤就是将数据填充到java bean中。大概流程如下
- 创建字段值为空的java bean,也就是我们例子中new Student()
- 如果配置了自动映射,则会将数据填充到对应字段(驼峰自动映射)
- 根据resultMap中的result/id进行字段填充(手动映射)
我们重点看一下2.3步骤。
- 自动映射
如果我们在resultMap中配置了autoMapping,则返回配置的值。否则返回true(1.isNested方法上述传入的false(表示简单结果集处理),默认mappingBehavior为PARTIAL)
当判断自动映射为true时,会进入到applyAutomaticMappings。此时会创建一个autoMapping的list,然后遍历这个list,根据每一个column去拿到数据行中对应的值,给set进去,完成数据填充。
跟着源码走下去,发现autoMapping是根据resultWapper中mappedColumns决定的。
而这个的初始化也就是我们resultMap中的配置的。
例如在下面这个配置中。id,name,class_name都是属于mappedColumns,只有age字段属于unmappingColumns,也就是说只有age属于自动映射中的autoMapping
运行效果:
-
手动映射 手动映射整体流程跟自动映射类似,先取出mappedColumnNames。(resultMap中配置的属性)。然后根据字段名去结果行中去到对应的值,最后填充。跟自动映射有2点区别 1.增加了延迟加载逻辑,如果设置了延迟加载,则不会去处理该字段的值(延迟加载这里不做重点介绍,大家可以忽略相关代码,关注点在主要流程上) 2.如果是嵌套查询,会进行再一次的查询。
嵌套查询(association或者collection中带有select):
填充逻辑。如果没有嵌套查询,则通过字段名,属性值去拿到对应的结果。如果有嵌套查询,则会走到getNestedQueryMappingValue
拿到嵌套查询的相关参数,可以看到有mapper的具体查询方式(mapper.classMapper.queryByname),查询参数(二班),最后映射的属性(aclass(class对象))
如果没有嵌套查询的话就会走到loadResult.此时会再次进行一个查询。也就是说如果我们使用association或者collection中带有select。它会将这个select里面的查询再走一下查询流程。
最后将查询结果设置到参数里,完成数据填充。
总结
- 结果集处理所有代码都在DefaultResultSetHandler中
- 复杂、简单结果集的定义(resultMap中带有association或者collection标签并且association或者collection标签中不带有select为复杂,其余均为简单)
- 简单结果集处理流程(handleRowValuesForSimpleResultMap)
- 如果特殊配置了rowBound,则会跳过指定行
- 拿到结果集遍历,每一行都是以下处理方式
- 拿到该行对应的resultMap,如果配置了discriminator标签,则会根据结果具体数据来选择resultMap,绝大多数情况,方法返回的就是我们xml中配置的resultMap
- 如果配置了自动映射(简单结果集默认开启),则会根据驼峰自动映射。自动映射的列为sql语句中查询出来的数据减去resultMap中使用result配置的列(如果这块不理解可以往前看一下)
- 手动映射。也就是处理resultMap中使用result配置的列。如果配置了延迟加载,则不进行数据填充。如果resultMap中包含association或者collection中带有select,则会再次进行一次查询,最后将数据填充到复合对象中。如果是单纯的数据映射,则会根据列名,字段名,结果,进行属性填充。
- 最后将结果放在一个list中供后续使用