Mybatis核心类(三)-SqlSource、BoundSql

SqlSource

SqlSource 是mybatis中的一个接口,我们平时的mapper文件中的sql解析后的结果便存放在SqlSource中,在mybatis后续的sql操作流程中,都会间接的获取到sql方法对应的SqlSource对象,再进一步处理得到此次sql操作的sql语句,入参等信息。

SqlSource实现类简介

1.RawSqlSource

用于对不存在动态标签的sql进行处理。

2.DynamicSqlSource

用于对存在动态标签的sql进行处理。

3.StaticSqlSource

存储最终的sql模板。RawSqlSourceDynamicSqlSource会先一步步的解析过程,如动态标签判断,非参数替换等。完成后都会创建StaticSqlSource对象,用于后续sql的实际执行,避免重复解析。

4.ProviderSqlSource

此实现类略,以下流程也不进行说明。

SqlSource的创建流程

一个mappper xml文件下的sql标签,对应一个SqlSource。我们看看SqlSource是如何创建的

1.RawSqlSource、DynamicSqlSource的创建

定位到关键代码XMLStatementBuilder#parseStatementNode

1.SqlSource是通过LanguageDriver#createSqlSource创建的

2.创建后的SqlSource会存入MappedStatement中,用于后续查询使用。

继续看LanguageDriver#createSqlSource后的方法,定位到关键方法XMLScriptBuilder#parseScriptNode,这里解释了不同SqlSource类型对象的创建逻辑。

如果SqlSource是动态的,就生动动态类型的SqlSource,否则生成真实类型的SqlSource

关键问题来了,什么样的sql标签才算是动态的呢? 看下isDynamic值逻辑的关键方法XMLScriptBuilder#parseDynamicTags

  1. 如果sql xml下的节点类型为CDATA_SECTION_NODE 或者TEXT_NODE ,且节点isDynamic,则说明当前sql是动态的,即isDynamictrue
  2. 如果sql xml下的节点类型为ELEMENT_NODE ,会获取对应元素的解析器进行处理,且当前sql是动态的,即isDynamictrue

以上两种情况,代表sql标签是动态的。

下面稍微解释下CDATA_SECTION_NODE ,TEXT_NODE ,ELEMENT_NODE 这几个节点类型

1. CDATA_SECTION_NODE

用于存储不需要转义的文本。

该示例中, <![CDATA[ select * from user where userName like #{userName} ]]> ,为一个CDATA 类型的节点,值为select * from user where userName like #{userName}

2. ELEMENT_NODE

代表节点为一个元素。

该示例中<where> user_name = ${userName} </where>为一个ELEMENT_NODE 节点,值为 user_name = ${userName}

这种类型的节点,mybatis会根据节点名称,如where,if,foreach等获取对应的节点解析器进行sql解析。

3. TEXT_NODE

代表节点为一个文本值。

跟上面同样的示例,select * from user是一个TEXT_NODE 节点,值为select * from user

以上便是相关XML节点类型的说明,回到isDynamic值的判断条件,其中一条是

如果sql xml下的节点类型为CDATA_SECTION_NODE 或者TEXT_NODE ,且节点为isDynamic,则说明当前sql是动态的,即isDynamictrue

现在已经知道什么节点类型才为CDATA_SECTION_NODE 或者TEXT_NODE 了,接着说明什么类型的节点为isDynamic

核心方法为TextSqlNode#isDynamic创建了一个文本解析器,解析文本节点中是否存在${开头,}结尾的文本块,如果存在,节点则是动态的,值isDynamictrue

以上便是sql语句对应SqlSource类型的代码逻辑

总结:

1. sql语句标签中,存在 ${xxx} 格式的文本块,则创建的SqlSource类型为DynamicSqlSource

2. sql语句标签中,存在子级元素,如 <where> <if> <foreach> 等,则创建的·SqlSource类型为 DynamicSqlSource

3. 其他情况,创建的SqlSource类型为RawSqlSource**

2.StaticSqlSource

以上便为 DynamicSqlSourceRawSqlSource 类型对象的创建过程,接下来继续说StaticSqlSource类型对象的创建过程。

StaticSqlSource对象是由 DynamicSqlSourceRawSqlSource 类型对象进一步解析而来的,是mybatis查询过程中最终使用到的类型。DynamicSqlSourceRawSqlSource 类型的对象只是一个中间状态。

关键方法为SqlSourceBuilder#parse,整个项目中只有这里创建了StaticSqlSource对象,而该方法也只有DynamicSqlSourceRawSqlSource 进行了调用。

可以得出初步结论: StaticSqlSource对象是由 DynamicSqlSourceRawSqlSource 类型对象进一步解析而来的

分析下SqlSourceBuilder#parse做了什么事情

  1. 创建一个针对#{xxx}格式字符串的解析器GenericTokenParser

  2. 调用GenericTokenParser#parse,对DynamicSqlSourceRawSqlSource中的sql进行进一步解析,如果带有#{xxx}格式的字符串,则替换为'?'这里便是#{xxx}格式的参数会被预编译的原因

说下是怎么将#{xxx}格式字符串替换为'?'的,debug进GenericTokenParser#parse,如果匹配到#{xxx}格式的字符串,会将sql中#{xxx}格式字符串的位置,添加为TokenHandler#handleToken的返回值,这里看到是一个'?'

此次解析的sql

进去TokenHandler#handleToken看看,这里会将#{userName}的userName放入存放参数key的list parameterMappings中,然后返回'?'

StaticSqlSource被调用的地方

以上便是SqlSourceBuilder#parse方法的解析,继续看DynamicSqlSourceRawSqlSource调用该方法的地方

RawSqlSource

生成了一个StaticSqlSource作为成员变量,在getBoundSql方法中被实际用到,调用RawSqlSource#getBoundSql时会调用StaticSqlSource#getBoundSql

DynamicSqlSource

每次被调用getBoundSql方法时,都会创建一个StaticSqlSource对象,调用StaticSqlSource#getBoundSql

总结:

1. StaticSqlSource对象是由 DynamicSqlSourceRawSqlSource 对象进一步解析而来的。如果DynamicSqlSourceRawSqlSource的sql中存在#{xxx}字符串,会被替换为'?',这个是#{xxx}参数会被预编译的原因。

2. StaticSqlSource是sql标签解析后的结果,在sql查询时会被mybatis调用到getBoundSql方法。

BoundSql

mybatis执行sql操作时,每次都会创建一个BoundSql对象,获取sql内容,入参,映射等信息的。

1.BoundSql的创建流程

通过上文可知, BoundSql是调用 DynamicSqlSourceRawSqlSourcegetBoundSql生成的。这两个类又是调用StaticSqlSource#getBoundSql生成的BoundSql对象,再进行进一步加工。

RawSqlSource

RawSqlSource#getBoundSql没有其他操作,单纯调用StaticSqlSource#getBoundSql返回值

DynamicSqlSource

流程如下:

1. 根据入参情况拼接最终的sql。因为是动态类型的sql,所以存在<where> <if> <foreach> 等标签,需要在查询的时候才能进行sql解析拼接。

2. 拼接的如果是字符串,不是标签节点,如果字符串存在${xxx},会替换为入参xxx字段对应的值。

3. 拼接完的sql,如果存在#{xxx},会将入参xxx的值存起来,并将sql此处替换为'?'。

2. 为什么需要使用BoundSql而不直接用SqlSource

我们需要先知道,BoundSql存在的意义,如果要获取sql的话直接通过SqlSource获取不就行吗,何必多加个类呢?以下原因为个人总结,可能存在歧义,参考即可。

因为SqlSource只存储了sql本身的信息,并不存储每次查询实际的参数,SqlSource可以理解为一个中间状态的sql模板。

在sql解析的时候。获取到sql模板后,还要做最后一步操作:入参拼接,才能生成最终执行的sql,存进BoundSql

3.BoundSql在sql操作时被使用的地方

PreparedStatementHandler#instantiateStatement,这是mybatis创建Statement对象,再去jdbc执行操作的方法,调用boundSql.getSql()获取到了最终被执行的sql。

DefaultParameterHandler#setParameters,调用boundSql.getParameterMappings()获取了此次sql的参数,再调用typeHandler#setParameter将参数值放进Statement对象中,接着调用jdbc相关接口,完成此次sql操作。

相关推荐
听见~7 小时前
MyBatisPlus
mybatis
向阳121812 小时前
mybatis SqlSessionFactory
java·mybatis
Suwg20912 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式
秋恬意14 小时前
MyBatis动态 SQL 的执行原理
mybatis
青年有志17 小时前
深入浅出 MyBatis | CRUD 操作、配置解析
数据库·tomcat·mybatis
00Allen0017 小时前
mybatis/mybatisplus
java·spring·mybatis
Echo flower17 小时前
mybatis-plus自动填充时间的配置类实现
java·数据库·mybatis
编码浪子18 小时前
Springboot3国际化
java·spring·mybatis
Yan.love19 小时前
【MyBatis 核心工作机制】注解式开发与动态代理原理
java·mybatis
油丶酸萝卜别吃1 天前
MyBatis中XML文件的模板
xml·数据库·mybatis