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操作。

相关推荐
haozihua2 小时前
4.Mybatis中,在Mapper的SQL映射文件中,使用<choose><when>无法识别参数的情况
java·sql·mybatis
Onlooker1299 小时前
MyBatis5-缓存
缓存·mybatis
天幕繁星9 小时前
JSqlParser、JavaCC实操
mybatis·mybatis plus·jsqlparser·javacc
漫天转悠10 小时前
SQL注入攻击及其在SpringBoot中使用MyBatisPlus的防范策略
spring boot·mybatis
鹿屿二向箔12 小时前
基于SSM框架(Spring, Spring MVC, MyBatis)的矿场仓储管理系统的基础示例
spring·mvc·mybatis
魔道不误砍柴功19 小时前
简单叙述 Spring 是如何解决循环依赖问题的呢?
java·spring·mybatis
666786661 天前
常见异常归总
java·mysql·mybatis
晨曦蜗牛1 天前
Spring Boot 3中基于纯MyBatis的CURD开发实例
spring boot·后端·mybatis
鹿屿二向箔1 天前
基于 SSM(Spring + Spring MVC + MyBatis)框架构建电器网上订购系统
spring·mvc·mybatis
CoderIsArt1 天前
Redis的缓存问题与应对策略
redis·缓存·mybatis