mybatis源码(3)-简单结果集处理

127 阅读7分钟

在文章Mybatis缓存详解中我们说了mybatis的查询流程,从sqlSession到二级缓存到一级缓存再到数据库查询 ,当我们查询时,走到SimpleExecutor进行具体数据库查询时,有如下流程

image.png

statementHandler

这里简单介绍一下statementHandler.该对象负责操作 Statement 对象与数据库进行交流,并且还会通过内部的ParameterHandler 和 ResultSetHandler 对参数进行映射,对结果进行实体类的绑定。之前我们说过executor是sqlSession背后的操作者,其实statementHandler才是executor背后的操作者。

image.png 类结构与executor类似,但我们生产中99%的场景都是使用preparedStatementHandler(默认也是preparedStatementHandler)。并且routingStatementHandler其实是一个简单工厂。根据我们配置来选初始化具体的handler 通过configuration来初始化statementHandler image.png image.png 使用statementHandler进行查询

image.png ResultSetHandler只有一个子类,DefaultResultSetHandler。Mybatis的结果集处理代码全部在里面。这也是个人觉得Mybatis源码中最复杂的一块。所谓结果集处理,就是将jdbc中查询到的结果(resultSet)填充到xml中使用resultType/resultMap配置的java bean中。

如下图,我们99.99%的查询只会走下图上面部分代码。对下部分代码场景大家可以参考官网中例子。我们后续只会对上部分代码进行详细说明。 多结果集说明 核心方法为handleResultSet

image.png 我们在日常开发中几乎很少去配置resultHandler。我们可以自定义resultHandler来决定从db result到java bean的映射。(有兴趣的同学可以自行查一下)。而默认的DefaultResultHandler里面其实就一个list,默认会将解析过后的数据添加到list中。 image.png

结果集处理核心方法就是下面的handleRowValues。他的大概流程如下

1.判断是复杂结果集还是简单结果集。走各自的流程(这里着重介绍简单结果集)

2.根据结果集个数,循环处理每一行。

image.png

结果集定义

这里我们先介绍一下两种结果集的定义

  • 复杂结果集
    • 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,整个查询就定义为简单查询 image.png

nestedResultMapId构建,可以看到是通过nestedResultMap是否为null来进行构建。

image.png

image.png 我们继续往前看nestedResultMap,我们常见的xml方式是通过XMLMapperBuilder.buildResultMappingFromContext进行构建

image.png 核心方法就是 processNestedResultMappings,代码非常简单。也跟我们上述复杂结果集定义对应上了。如果包含association或者collection或者case标签并且在这些标签中没有select语句,则定义为复杂结果集

image.png

简单结果集处理

1.跳过指定行
2.根据结果一行一行解析数据
2.1 得到该行记录对应的resultMap
2.2 通过resultMap与resultSet解析该行(重点)
2.3 保存记录 image.png

跳过指定行
得到对应的resultMap

有这样一个场景,如果我们想通过查出来的某个字段的不同值来选择不同的映射模型,比如上述的student模型中,如果班级名称为二班,则不进行班级数据的关联查询。在resultMap的配置中,mytatis为我们提供了discriminator标签来实现,跟sql中的case when用法基本一致。

image.png 所以在源码resolveDiscriminatedResultMap方法中这个步骤的目的就是通过该数据行这个字段的value来选择具体的resultMap.如果我们不使用discriminator标签(日常开发还是适用得比较少的),那这个方法返回的就是本身查询的resultMap. resultType也会封装称一个resultMap,只不过里面的各项参数都为null。

数据解析(重点)

我们已经拿到了数据行,以及数据行对应的resultMap,而resultMap也是对应一个简单/复杂的java bean,这步骤就是将数据填充到java bean中。大概流程如下

  1. 创建字段值为空的java bean,也就是我们例子中new Student()
  2. 如果配置了自动映射,则会将数据填充到对应字段(驼峰自动映射)
  3. 根据resultMap中的result/id进行字段填充(手动映射)

image.png

我们重点看一下2.3步骤。

  • 自动映射 如果我们在resultMap中配置了autoMapping,则返回配置的值。否则返回true(1.isNested方法上述传入的false(表示简单结果集处理),默认mappingBehavior为PARTIAL) image.png

image.png

当判断自动映射为true时,会进入到applyAutomaticMappings。此时会创建一个autoMapping的list,然后遍历这个list,根据每一个column去拿到数据行中对应的值,给set进去,完成数据填充。

image.png 跟着源码走下去,发现autoMapping是根据resultWapper中mappedColumns决定的。 image.png

image.png

image.png

而这个的初始化也就是我们resultMap中的配置的。 image.png

例如在下面这个配置中。id,name,class_name都是属于mappedColumns,只有age字段属于unmappingColumns,也就是说只有age属于自动映射中的autoMapping

image.png

运行效果:

image.png

  • 手动映射 手动映射整体流程跟自动映射类似,先取出mappedColumnNames。(resultMap中配置的属性)。然后根据字段名去结果行中去到对应的值,最后填充。跟自动映射有2点区别 1.增加了延迟加载逻辑,如果设置了延迟加载,则不会去处理该字段的值(延迟加载这里不做重点介绍,大家可以忽略相关代码,关注点在主要流程上) 2.如果是嵌套查询,会进行再一次的查询。 image.png

    嵌套查询(association或者collection中带有select):

image.png 填充逻辑。如果没有嵌套查询,则通过字段名,属性值去拿到对应的结果。如果有嵌套查询,则会走到getNestedQueryMappingValue

image.png

image.png 拿到嵌套查询的相关参数,可以看到有mapper的具体查询方式(mapper.classMapper.queryByname),查询参数(二班),最后映射的属性(aclass(class对象)) image.png 如果没有嵌套查询的话就会走到loadResult.此时会再次进行一个查询。也就是说如果我们使用association或者collection中带有select。它会将这个select里面的查询再走一下查询流程。 image.png 最后将查询结果设置到参数里,完成数据填充。

总结

  • 结果集处理所有代码都在DefaultResultSetHandler中
  • 复杂、简单结果集的定义(resultMap中带有association或者collection标签并且association或者collection标签中不带有select为复杂,其余均为简单)
  • 简单结果集处理流程(handleRowValuesForSimpleResultMap)
    • 如果特殊配置了rowBound,则会跳过指定行
    • 拿到结果集遍历,每一行都是以下处理方式
      • 拿到该行对应的resultMap,如果配置了discriminator标签,则会根据结果具体数据来选择resultMap,绝大多数情况,方法返回的就是我们xml中配置的resultMap
      • 如果配置了自动映射(简单结果集默认开启),则会根据驼峰自动映射。自动映射的列为sql语句中查询出来的数据减去resultMap中使用result配置的列(如果这块不理解可以往前看一下)
      • 手动映射。也就是处理resultMap中使用result配置的列。如果配置了延迟加载,则不进行数据填充。如果resultMap中包含association或者collection中带有select,则会再次进行一次查询,最后将数据填充到复合对象中。如果是单纯的数据映射,则会根据列名,字段名,结果,进行属性填充。
    • 最后将结果放在一个list中供后续使用