我们知道,MyBatis是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和Java的POJOs(普通的Java对象)映射成数据库中的记录。 可爱的小鸟,大家应该很熟悉这个小鸟吧 所以,就目前而言,MyBatis基本上是中小公司通用的持久层框架,简单,易上手,方便快捷,成为最受欢迎的持久层框架,远远甩开Hibernate几条街。 扯远了,回到我们文章的正题上来: 今天公司一同事出去面试的时候,阿里大厂面试官问了一个Mybatis源码上的问题,由于他没有注意这块,没有答到核心上,结果面试官直接终止了面试流程。很尴尬… 面试问题:请描述一下,Mybatis是如何将查询结果集映射到实体类上的?重点阐述下实体类对象是如何构造出来的? 好吧,那我们就来对这个面试题进行分析一下,MyBatis是如何将数据库查询的结果集映射到我们的实体类上的,分析实体类对象是怎么构造出来的。 下面咱们就开始源码表演了 图1:直接进入到主题 上图中,handler就是Mybatis自己实现了JDBC标准接口Statement 的一个子类PrepareStatement 对象,该对象实际上是调用Connection.createStatement(sql) 来获取的。 重点: 大家注意看上图中红色线框地方的源码,那里就是对结果集和JAVA实体类的一个映射处理。我们进入到方法内部去看看它的处理逻辑 图2:handleResultSets方法处理逻辑 我们分析一下 【1】rsw: 这里其实就是ResultSet 的包装对象,包含了数据库结果集ResultSet,以及其它的一些元数据。 【2】resultMaps: 这个实际上就是我们XML里面配置的返回对象,一般情况下只会有一条数据。所以,下面的while循环也只会循环一次,说到这里大家应该明白while的作用了吧,其实核心逻辑就是将数据库的结果集映射到我们需要的类型上去。 【3】handleResultSet:就是具体处理的方法, 我们一路点进去,发现这里: 图3:这里就开始对结果集进行遍历 这个方法就是从包装对象rsw中取得ResultSet结果集,进行遍历,这个写法我相信大部分人都会,就是我们以前写JDBC的时候,循环设值是最基本的操作吧。 继续深入分析getRowValue方法,我们发现createResultObject方法 图3:创建结果集对象方法 这里有个重点对象:metaType ,它是我们实体类的元信息,包括了字段信息,get和set信息,默认构造器等等。 那么metaType 是怎么拿到的呢?我想大家都很清楚,那肯定是通过反射来获取的了。myBatis又是怎么获取的呢?跟踪源码我们发现,在MetaClass 构造器实例化对象的时候,调用反射工厂来获取的,封装为reflector 对象,里面就包含了我们类的元信息。具体信息如下: 图4:MetaClass的Reflector对象信息 好了,回到createResultObject方法 ,我们继续跟踪发现,myBatis需要进行自动匹配类对象的构造器。我们继续看createByConstructorSignature() 方法,这个方法就是推断构造器的核心源码了。 图5:推断构造器核心方法 我们可以很明显的看到,myBatis分2步走,一个是拿到全部的显示构造器列表,一个是获取默认构造器。如果有默认构造器,则优先使用默认构造器;否则:就需要继续推断合适的构造器了。 我们继续看看,是如何推断的呢? 图5:推断过程 好吧,看到这里,你觉得是不是很扯淡?myBatis居然是通过参数个数来判断的,而且只支持全参构造器,少一个都不行,最后的for循环仅仅是对参数类型进行了校验。这块确实不如Spring的灵活强大。 到这里,我们得到了实体类的构造器,回到MyBatis,看看是如何赋值的呢?我们先看看源码 图6:Mybatis赋值的核心方法 核心: 这里就是拿到我们上面推断出来的构造器,遍历它的构造器参数类型列表,对构造器的每一个参数类型推断出一个类型处理器typeHandler,如:IntegerTypeHandler、StringTypeHandler 。 我们继续看看具体的值是如何获取到的:Object value = typeHandler.getResult(rsw.getResultSet(), columnName); //源码片段 我们看到,通过上面的类型处理器,从ResultSet中获取对应的字段的值,这个咋获取到的值,我就不说了吧,这个是JDBC的基础知识,即ResultSet.getString(column) 来获取的。 循环体中,就是将每一个参数类型以及参数值一一对应放到constructorArgTypes 和constructorArgs 中去。循环结束后,调用objectFactory.create(resultType, constructorArgTypes, constructorArgs) ,你们知道这句代码是干什么用的吗? 哈哈,用大腿猜也知道,肯定是生成对象嘛,如何生成对象呢? 图7:生成对象的最终核心逻辑 O( _ )O哈哈~,看到这里,我们知道了MyBatis对数据库结果集映射的核心思想了,看到这里你知道该如何回答面试官的哪个问题了吗? 归根到底层,MyBatis用的是JAVA的反射那一套,没有什么创新的地方,以及对ResultSet的操作,也是很基础的知识了,这些我就不多说了。 我们可以看到,框架都是万变不离其宗,只有我们对JAVA的基础知道掌握好了,其它的框架还不是手到擒来。 祝大家能够吊打面试官,拿到高薪offer,赢取白富美,走上人生巅峰!