1. 传统JDBC
Mybatis其实就是封装传统JDBC的,它和传统JDBC访问数据库基本一模一样。因此,不要觉得Mybatis有多高级。而 ResultSetHandler 就是处理我们JDBC访问数据库获取到的ResultSet结果集的。在此之前,我们还是先看一下传统JDBC:
再次基础之上,我们继续分析Mybatis是如何访问数据库并且封装对象的。
2. Mybatis访问数据库
中间涉及到设置参数,初始化Statement等操作,这些在MyBatis源码分析_Executor组件及3个火枪手(6)_chen_yao_kerr的博客-CSDN博客已经 分析过了。接下来将是直接分析访问数据库以及返回结果集的处理。
最终,他会调用到 RoutingStatementHandler的query方法。而我们在实例化RoutingStatementHandler的时候,我们说过它是典型的策略模式。它是根据xml配置文件的信息,生成不同的StatementHandler对象,而本文则是基于PreparedStamentHandler进行的,因此它必然会进入PreparedStamentHandler的query方法:
2.1 Statement访问数据库
最终调用的是mysql的底层jar包PreparedStatementLogger对象访问的数据库,这一点和JDBC访问的方式一模一样,我们就不做过多的分析了。
2.2 火枪手 ResultSetHandler 出现
访问数据库结束以后,我们会对调用ResultSetHandler对象对结果集进行mybatis特有的处理,这一点是和JDBC不同的:
而 StatementHandler 的基类中,是持有ResultSetHandler的:
所有,我们的PreparedStatementHandler才可以直接使用ResultSetHandler对象处理结果集ResultSet.
3. ResultSetHandler处理结果集
3.1 首先就是进入 handleResultSets 方法
a. 首先就是从Statement对象中获取第一个结果集ResultSet并包装成 ResultSetWrapper
b. 获取结果集对应的ResultMap,这个是在第一阶段加载xml文件的时候就准备好的,column-property的形式
c. 根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
d. 获取下一个结果集继续遍历,直到遍历完所有的结果集位置。
3.2 handleResultSet 方法根据映射规则(resultMap)对结果集进行转化
其实,这个方法就是对结果集进行缓存处理,并且在填充完以后放入list中。
3.3 handleRowValuesForSimpleResultMap 方法对行进行映射处理
针对读取ResultSet对象进行映射并且保存映射结果,我将单独开一个大的段落进行分析。本篇后面的所有内容都是针对此处进行详细分析。
4. getRowValue方法针对读取ResultSet对象进行映射(3.3细化)
首先看一下getRowValue方法的总体结构,后面将针对这个结果逐步分析每一步都干了什么事情
4.1 根据resultMap的type属性,实例化目标对象
这一步挺简单的,就是根据我们配置的返回值类型,实例化出来一个空的对象,方便后面的步骤进行值的设置
4.2 对目标对象进行封装得到metaObjcect,为后续的赋值操作做好准备
元数据对象,由Configuration对象负责生成,它是mybatis提供的反射工具类。因为这是一个底层代码,使用反射设置属性值是通用做法,而传统的java反射对处理List、Map、嵌套类等处理起来并不方便。而Mybatis提供的MetaObject对象,能够很好的处理集合、嵌套类等,功能更为强大。
生成对象以后,我们来看一下这个结构:
由于我们的测试case不涉及嵌套类型的查询,因此无法看到嵌套类型具体值的设置过程,后面会补一篇关于嵌套类型的博客,单独进行分析。
4.3一般情况下 autoMappingBehavior默认值为PARTIAL,对未明确指定映射规则的字段进行自动映射
由于我们是使用最简单的类型进行查询的,所以我们会把select语句中查询的字段进行自动映射。简单点说就是默认查询的字段 和 表中的字段名是一模一样的,这届根据当前的查询字段进行设置值就ok了。
而值的查询,就是和传统的JDBC代码一样,从ResultSet中获取并设置到目标对象中的:
可能有人会说,怎么是把值设置到了metaObject元数据中了呢? 其实,元数据中之前提过了,是会生成BeanWrapper对象的,这就是一个Bean对象。我们最终是通过反射,设置到这个bean对象中的:
4.4 映射resultMap中明确指定需要映射的列
这一章节,是我们在xml文件中配置了ResultMap的映射关系,按照映射关系进行设置值的,逻辑与4.3雷同
4.5 如果没有一个映射成功的属性,则根据<returnInstanceForEmptyRow>的配置返回null或者结果对象
这一步就没有什么好说的了, rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
5. 保存映射结果对象
其实,大部分的映射都是放置在 resultHandler 和 resultContext中的。
5.1 resultContext 计数作用
5.2 ResultHandler存储结果集
也就是说,能够获取到ResultHandler对象,就可以获取到封装好了的所有结果集了。
6. 主流程over
待我们获取到ResultHandler对象以后,就可以从ResultHandler对象中获取到所有的结果集,并放入名称为multipleResults的List中。
然后就是一路返回List到Executor组件中;
接着返回,因为我们查询调用的是SqlSession中的 selectOne 方法,所有只会返回1条数据。
而调用的入口方法在此处:
至此,全部调用完毕,返回到我们自己写的业务代码处。