Mybatis源码主流程串讲(二):Configuration对象

180 阅读4分钟

接上回Mybatis源码主流程串讲(一):配置的解析。上文书我们讲到,在启动的时候,mybatis利用SqlSessionFactoryBuilder对象对所有相关的xml文件进行解析,并形成configuration对象放在内存中等待被使用。同时也提到,Configuration是一个串联全局的对象,在整个mybatis代码体系里面这个对象几乎无处不在;而且里面存储的参数,几乎囊括了mybatis所有运行需要使用到的内容。这次我们就来看看SqlSessionFactoryBuilder生成的Configuration里面存放的重点内容。

我们梳理的主要是mybatis运行的主流程,那么关注的也就是两部分:sql执行和结果封装。涉及到的configuration参数主要有两个——mappedStatement 以及 resultMaps:

image.png

图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类的各个参数:

image.png

图2 ResultMap局部

进一步往里看,ResultMapping里面的参数是这样的:

image.png

图3 ResultMapping局部

仔细对一下,就可以发现,他跟mapper.xml文件里面的result标签里面的参数是能够对应上的。因此resultMap对象里吗的三个list里面就是存储的结果实体的各个参数和数据库column的映射。在后续查询结果封装实体的时候,就主要利用resultMapping里存储的class、column、property三个参数,利用反射生成对象。

MappedStatement存储的内容

上文提到了resultMaps用于存储封装查询结果的各个参数。那么mappendStatement存储的就是执行sql所需要的参数。那么猜想一下最重要的参数是什么呢?sql、传入sql的参数、封装结果的map,带着这个猜想,我们看看mappedStatement类:

image.png

图3 MappedStatement局部

很明显,mybatis的设计者跟咱想的一样(笑)。我们知道,mybatis最重要的功能就是能够动态的生成sql,并封装结果。封装结果上文咱说了,那么下面咱们聊聊动态生成sql的部分,也就是MappedStatement存储的SqlSource。SqlSource是一个接口,他有如下几个实现类:

image.png

图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在哪层~)

image.png

图5 DynamicSqlSource

可以看到拼接sql的部分是rootSqlNode是一个sqlNode接口,那么他有哪些实现呢?

image.png

图6 SqlNode的各个实现类

可以看到,这些对应着mapper文件里的节点等等,这些类就对应着解析不同的标签:
<if></if>  <trim></trim> <foreach collection=""></foreach>

等等。这些类里都有一个关键方法 apply(DynamicContext var1) 。他是真正生成sql语句的,不断地递归调用apply方法最后生成sql语句。我们看一个最简单的节点ifSqlNode:

image.png

图7 IfSqlNode类

进一步的,为什么需要递归呢?主要是因为这些sql节点都是可以嵌套的,我们也不清楚究竟嵌套了几层,所以需要递归解析。那么什么时候结束呢?请看 sqlNode的其中一个实现类 StaticTextSqlNode的context方法:

image.png

图8 StaticTextSqlNode类

再看另一个实现类:TextSqlNode :

image.png

图8 TextSqlNode类

现在我们整体来看看一个动态的sql是咋生成的:

image.png

图9 一个sql是怎么一层一层嵌套起来的

上面那个图就很清楚了,一层一层的嵌套,一层一层的判断,最后会递归到TextSqlNode或者StaticTextSqlNode,进行sql的拼装或者进行ognl表达式的解析。至此,我们最关键的动态sql的生成就完成了。

至此,我们梳理了sql是怎么生成的,result实体是怎么映射的,后面我们会继续探索mybatis是如何执行sql的。 (未完待续......)