接上回Mybatis源码主流程串讲(一):配置的解析。上文书我们讲到,在启动的时候,mybatis利用SqlSessionFactoryBuilder对象对所有相关的xml文件进行解析,并形成configuration对象放在内存中等待被使用。同时也提到,Configuration是一个串联全局的对象,在整个mybatis代码体系里面这个对象几乎无处不在;而且里面存储的参数,几乎囊括了mybatis所有运行需要使用到的内容。这次我们就来看看SqlSessionFactoryBuilder生成的Configuration里面存放的重点内容。
我们梳理的主要是mybatis运行的主流程,那么关注的也就是两部分:sql执行和结果封装。涉及到的configuration参数主要有两个——mappedStatement 以及 resultMaps:
图1 Configuration局部
两者均定义为了map,这么设计也很容易理解,定义的时候每个标签都会定义一个id,例如:<sql id="Common_Column_List">
id, student_code, student_name, score
</sql>
<select id="selectStudent" resultMap="MatchMap">
select <include refid="Common_Column_List">
from student
</select>
<resultMap id="MatchMap" type="com.xx.xx.xx.Student">
<result column="id" jdbcType="VARCHAR" property="id"/>
<result column="student_code" jdbcType="VARCHAR" property="studentCode"/>
<result column="student_name" jdbcType="VARCHAR" property="studentName"/>
<result column="score" jdbcType="VARCHAR" property="score"/>
</resultMap>
在使用的时候mybatis需要根据这些id获取到mapper文件里配置的各个内容,存储为map更方便使用。按照惯例,先放个大概的层次图放着:
resultMaps存储内容
我们来看看ResultMap类的各个参数:
图2 ResultMap局部
进一步往里看,ResultMapping里面的参数是这样的:图3 ResultMapping局部
仔细对一下,就可以发现,他跟mapper.xml文件里面的result标签里面的参数是能够对应上的。因此resultMap对象里吗的三个list里面就是存储的结果实体的各个参数和数据库column的映射。在后续查询结果封装实体的时候,就主要利用resultMapping里存储的class、column、property三个参数,利用反射生成对象。MappedStatement存储的内容
上文提到了resultMaps用于存储封装查询结果的各个参数。那么mappendStatement存储的就是执行sql所需要的参数。那么猜想一下最重要的参数是什么呢?sql、传入sql的参数、封装结果的map,带着这个猜想,我们看看mappedStatement类:
图3 MappedStatement局部
很明显,mybatis的设计者跟咱想的一样(笑)。我们知道,mybatis最重要的功能就是能够动态的生成sql,并封装结果。封装结果上文咱说了,那么下面咱们聊聊动态生成sql的部分,也就是MappedStatement存储的SqlSource。SqlSource是一个接口,他有如下几个实现类:图4 SqlSource的实现类
其中,比较重要的是DynamicSqlSource 和 StaticSqlSource 。从名字上看,两个类分别解析的是动态sql和静态sql的实现类。StaticSqlSource相对简单,他解析的是mapper里一些写死的sql。例如:<select id="selectStudent" resultMap="MatchMap">
select id, studentCode,studentName
from student
where data_state = 1
</select>
他的功能就比较简单了,存下来这个sql,执行的时候再返回去就行了,大家可以自己去看看StaticSqlSource的实现,不难理解。
现在就落到了重点上:DynamicSqlSource —— 动态生成sql,我们来看看他的实现:
(PS:这个DynamicSqlSource在哪设置的?给大家留个小思考题,可以看看XMLMapperBuilder类的parsePendingStatements方法,一层一层找找看,搜搜sqlSource在哪层~)
图5 DynamicSqlSource
可以看到拼接sql的部分是rootSqlNode是一个sqlNode接口,那么他有哪些实现呢?图6 SqlNode的各个实现类
可以看到,这些对应着mapper文件里的节点等等,这些类就对应着解析不同的标签:<if></if> <trim></trim> <foreach collection=""></foreach>
等等。这些类里都有一个关键方法
apply(DynamicContext var1)
。他是真正生成sql语句的,不断地递归调用apply方法最后生成sql语句。我们看一个最简单的节点ifSqlNode:
图7 IfSqlNode类
进一步的,为什么需要递归呢?主要是因为这些sql节点都是可以嵌套的,我们也不清楚究竟嵌套了几层,所以需要递归解析。那么什么时候结束呢?请看 sqlNode的其中一个实现类 StaticTextSqlNode的context方法:
图8 StaticTextSqlNode类
再看另一个实现类:TextSqlNode :图8 TextSqlNode类
现在我们整体来看看一个动态的sql是咋生成的:图9 一个sql是怎么一层一层嵌套起来的
上面那个图就很清楚了,一层一层的嵌套,一层一层的判断,最后会递归到TextSqlNode或者StaticTextSqlNode,进行sql的拼装或者进行ognl表达式的解析。至此,我们最关键的动态sql的生成就完成了。至此,我们梳理了sql是怎么生成的,result实体是怎么映射的,后面我们会继续探索mybatis是如何执行sql的。 (未完待续......)