Mybatis(八)动态Sql的实现原理

news2025/6/17 10:14:49

一、动态Sql的使用

顾名思义,动态sql值得是事先无法预知具体条件,需要在运行时根据具体的情况动态生成Sql语句。例如:

 <sql id="userAllField">
      id,create_time, name, password, phone, nick_name
    </sql>

    <select id="getUserByEntity"  resultType="com.blog4java.mybatis.example.entity.User">
        select
        <include refid="userAllField"/>
        from user
        <where>
            <if test="id != null">
                AND id = #{id}
            </if>
            <if test="name != null">
                AND name = #{name}
            </if>
            <if test="phone != null">
                AND phone = #{phone}
            </if>
        </where>
    </select>

上面的代码中,当我们不确定是否有查询条件时,可以使用<where>、<if>等标签,通过OGNL表达式判断参数内容是否为空,如果表达式结果为true,mybatis则会自动拼接<if>标签内的sql内容,否则会忽略相应内容。<where>标签是保证至少有一个查询条件时则会在sql语句中追加where关键字,同时还能剔除后面紧跟着的or或者and关键字。

除了上面的几个标签外,mybatis的动态标签还有下面几个:

<choose|when|otherwise>这几个标签是搭配使用的,类似于java中的switch语法:

这组标签和java中的switch类似,when和otherwise条件都是互斥的 ,当任一when标签符合则其他标签不会走。

<foreach>:该标签用于对集合参数进行遍历,通常用于构建in或者批量操作等语句,例如:

<select id="getUserByPhones"  resultType="com.blog4java.mybatis.example.entity.User">
        select
        <include refid="userAllField"/>
        from user
        where phone in
        <foreach item="phone" index="index" collection="phones"
                 open="(" separator="," close=")">
            #{phone}
        </foreach>
    </select>

<trim | set>标签:这两个标签和<where>标签类似,用于避免多余的and、or关键字,<set>自居中多余的逗号问题等:

例如:

这种情况可以用<where>标签解决,也可以用<trim>标签解决:

<set>标签和<trim>标签类似,主要用来避免set语句中出现多余的逗号。

二、SqlSource 与 BoundSql详解

SqlSource用于描述sql资源,前面说过mybatis可以通过两种方式配置sql信息,一种通过注解(@Select等注解),一种通过xml(<select>等标签)。而SqlSource就是用来描述这些的sql资源(之前不是说存在MappedStatement???)。看下SqlSource接口定义:

该接口只有一个getBoundSql方法,该方法返回一个BoundSql实例,该对象是对Sql语句以及参数信息的封装,他是SqlSource解析后的结果。SqlSource有四个实现,分别是StaticSqlSource、DynamicSqlSource、RawSqlSource、ProviderSqlSource。

这四种实现类的作用如下:

 

 无论是注解还是xml方式,在Mapper调用时都会根据用户传入的从参数将Mapper配置转换为StaticSqlSource,我们先来看看这个类:

SqlSource包含Configuration,Configuration又包含MappedStatement,而MappedStatement又包含Configuration。

 StaticSqlSource的内容比较简单,只封装了解析后的Sql和参数映射信息,Executor与数据库交互除了需要参数映射信息外,还需要参数信息(存在BoundSql中),因此Executor并不是直接通过StaticSqlSource对象完成数据库操作的,而是与BoundSql交互,BoundSql是对Executor组件执行sql所需信息的封装,具体代码如下:

 如上代码,Boundsql不仅封装了Mapper解析后的sql语句和参数映射信息(也存在StaticSqlSource),还包含Mappeer调用时需要传入的参数对象。

 那这里有个疑问,MappedStatement、SqlSource、BoundSql到底存了哪些Mappper信息?

(SqlSource是<select|delete|update|insert>标签上的sql配置+Mapper入参等信息???

MappedStatement是XML文件的各种配置信息???)

(MappedStatement包含SqlSource对象属性,而SqlSource又包含SqlNode(动态标签对象),而通过调用SqlSource的getBoundSql方法可以获取信息数据更加全面的BoundSql)

三、LanguageDriver

上面讲到Mybatis通过SqlSource描述xml文件或者注解中配置的sql资源,那么sql配置信息是如何转换为SqlSource对象的?该过程就是由LanguageDriver组件完成的,先看下这个接口的定义:

该接口一共有三个方法,其中createParameterHandler方法用来创建ParameterHandler对象,另外还有两个重载方法createSqlSource,这两个方法用于创建SqlSource对象。

LanguageDriver接口有两个实现类,一个是XMLLanguageDriver,一个RawLanguageDriver。前者为XML语言驱动,能够适用于动态sql(为XML提供通过各种动态标签结合OGNL表达式语法实现动态sql的功能),而后者只支持静态sql。

所以我们重点来看看XMLLanguageDriver实现类的内容:

 

 下面看看注解怎么使用动态sql:

 

 

四、SqlNode

SqlNode用于描述Mapper Sql配置中的sql节点,他是实现动态sql的基石,首先来看看该接口:

该接口只有一个apply方法,该方法用来解析sql节点,根据参数信息生成静态sql内容 ,该方法需要一个DynamicContext对象作为参数,该对象分支了Mapper调用时传入的参数信息以及Mybatis内置的_parameter和_databaseId参数。

我们在使用动态sql时,动态sql标签都对应着一种具体的SqlNode实现类,具体如下:

作用具体如下:

 

 知道各个实现类后,接下来我们了解一下SqlNode与动态sql配置之间的对应关系,例如:

 从mybatis动态sql的角度来看,他是由4个SqlNode对象构成的,该Mapper配置转为SqlNode代码如下:

 

上面的代码我们创建了一个StaticTextSqlNode和三个IfSqlNode来描述Mapper中动态sql的配置,其中IfSqlNode有一个StaticTextSqlNode和条件表达式组成。

接着用一个MixedSqlNode将这些SqlNode组合起来,这样就完成了通过java对象来描述动态sql配置。

SqlNode对象创建完毕后,我们就可以调用MixedSqlNode的apply方法根据参数内容动态生成SQL内容了,该方法接受了一个DynamicContext对象作为参数,该对象封装了mapper调用时的参数信息,最后动态sql的解析结果会封装在DynamicContext对象中,我们只需要调用其getSql方法即可获取解析后的sql语句,运行上面的代码后会生成下面的sql内容:

 

 接下来我们看看MixedSqlNode怎么将多个SqlNode组合构建成一个SqlNode对象的:

我们在看下apply方法:

 

我们在看看SqlNode的实现类之一的IfSqlNode代码:

 

 

五、动态Sql解析过程

上面我们讲了SqlSource用于描述XML文件或者注解配置的Sql资源信息,SqlNode用于描述动态标签等信息,LanguageDriver用于对Mappper Sql配置就那些解析将Sql配置转换为SqlSource对象。

首先我们需要先看看XMLLanguageDriver类的createSqlSource方法:

 

 

 

接下来我们看下NodeHandler接口的定义:

 

NodeHandler接口中只有一个handleNode方法,该方法解析一个动态sql标签对应的XNode对象和一个存放SqlNode对象的List,该方法会对XML标签进行街恶习,把生成的SQLNode对象添加到 

List对象中,我们可以以IfHandler举例:

在handlerNode方法中会继续调用XMLScriptBuilder类的parseDynamicTags方法完成对<if>标签节点的解析,将子节点转换为MixedSqlNode对象,然后获取test属性对应的OGNL表达,接着创建IfSqlNode对象添加到List中去。 parseDynamicTags方法我们前面已经说过了,就是获取当前节点的所有子节点,如果子节点内容为动态sql标签,则继续使用动态sql标签对应的NodeHandler进行处理(即递归的完成了所有动态sql标签的解析)

其他的SqlNode的实现类处理逻辑与之相似,例如下面的ForEachHandler类的代码:

需要注意的是,XMLScriptBuilder类的构造方法中,会调用initNodeHandlerMap方法将所有NodeHandler的实例注册到一个Map中,如下:

 

 需要解析相应的动态Sql标签时,只需要根据标签名获取对应的NodeHandler对象进行处理即可,而不用每次都创建对应的NodeHandler实例(这也是享元思想的应用)

上面是动态Sql配置转为SqlNode对象的过程,那么SqlNode对象如何根据调用Mapper时传入的参数动态生成SQL语句的?

我们先来回顾下XmlScriptBuilder类的parseScriptNode方法:

可以看到Sql标签解析完后,将解析后生成的SqlNode对象封装在SqlSource中,前面的学习我们知道,Mybatis的MappedStatement用于描述Mapper中的Sql配置,SqlSource创建完毕后会存放在MappedStatement对象中的SqlSource属性中,Executor组件操作数据库时会调用MappedStatement对象的getBoundSql方法获取BoundSql对象。(即解析后的SqlNode放在SqlSource,SqlSource又放在MappedStatement,而BoundSql中也会有SqlSource存在的数据信息,所以最后调用Mapper时需要的各种信息直接通过BoundSql对象获取)

 getBoundSql的代码如下:

 如代码所示,SqlSource对象调用getBoundSql方法,这个过程就完成了SqlNode对象解析成Sql语句的过程,我们可以看看其实现类DynamicSqlSource怎么去进行复写getBoundSql方法的:

 

 获取到动态sql解析后的sql内容后,还需要调用parse方法返回一个StaticSqlSource对象,这个对象是用来静态sql的,接下来我们需要了解下parse方法:

 

 

 

 

 到此就会将动态sql转换为可以执行的静态sql。

 

六、从源码角度分析#{}和${}的区别

我们先来看${}参数占位符的解析过程,当动态Sql配置中存在${}参数占位符时,mybatis会使用TextSqlNode对象描述对应的Sql阶段,在调用TextSqlNode对象的apply方法时会完成动态sql的解析,我们来看看TextSqlNode的apply方法:

进入createParser方法 :

该方法返回一个GenericTokenParser对象,知道openToken属性为"${",closeToken属性为“}”,解析参数占位符的过程在前面已经介绍过了,我们可以来回顾一下:

 

进入handleToken方法:

 该方法根据占位符名称获取相应的参数值,然后替换成对应的参数值,例如:

 

 小结:

到这里其实还需要对几个配置类的关系进行验证,例如MappedStatement、SqlSource、BoundSql、SqlNode等等 

 

aaaa

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过工具生成指定 类型 大小 文件

今天给大家介绍一个神器 首先 大家在开发过程中或许经常需要涉及到文件上传类的功能 需要测试文件过大 空文件等等清空 不同大小的文件 而这种文件大小是比较不好控制的 但大家可以下载我的资源 文件生成工具(可生成指定大小 类型文件) 下载下来里面就有一个 fileGeneration…

Mysql的用户管理与权限管理

文章目录用户管理创建用户查看用户字段解释hostUserselect_priv , insert_priv等修改密码修改用户删除用户权限管理查看权限查看当前用户权限查看某用户的全局权限查看某用户的某库的权限查看某用户的某个表的权限授予权限例一例二例三收回权限例一例二专栏目录请点击 用户管理…

目标检测框架yolov5环境搭建

目前&#xff0c;目标检测框架中&#xff0c;yolov5 是很火的&#xff0c;它基于pytorch框架&#xff0c;集成opencv等框架&#xff0c;项目地址&#xff1a;https://github.com/ultralytics/yolov5&#xff0c;对我来说&#xff0c;机器学习、深度学习才开始接触&#xff0c;本…

web前端 --- 表单标签

表单标签 --- 行内标签 描述&#xff1a;一个完整的表单标签通常由表单域、表单控件&#xff08;表单元素&#xff09;和提示信息三部分构成 作用&#xff1a;数据交互&#xff08;C/S&#xff09; &#xff08;1&#xff09;表单域 --- <form> <form>标签用于定义…

一款适合程序员的 Markdown 简历模版,拒绝花里胡哨

一款适合 IT 行业的 Markdown 简约简历模版&#xff0c;拒绝花里胡哨 开始使用 &#xff08;1&#xff09;安装 Markdown 编辑软件&#xff0c;推荐使用 Typora 编辑器&#xff08;免费版 Typora 百度网盘下载点此下载&#xff09;。 &#xff08;2&#xff09;下载本项目&am…

Ansys Speos | 实现车内氛围灯早期仿真验证

在本例中&#xff0c;将演示如何使用Speos进行RGB(红、绿、蓝)车内环境照明的早期研究&#xff0c;目的是在设计光导之前评估指定位置的照明效果。 使用到的产品Ansys Speos 2022 R2或更高版本&#xff0c;license为Enterprise版本能激活人眼视觉效果。 概览 在汽车行业&#…

Nginx的优化,安全与防盗链

1.Nginx的页面优化 1.1 Nginx的网页压缩 在Nginx的ngx_http_gzip_module压缩模块提供对文件内容压缩的功能。进行相关的配置修改&#xff0c;就能实现Nginx页面的压缩&#xff0c;达到节约带宽&#xff0c;提升用户访问速度 vim /usr/local/nginx/conf/nginx.conf http { ..…

#算法记录 | Day33 贪心算法

1005.K次取反后最大化的数组和 class Solution:def largestSumAfterKNegations(self, A: List[int], K: int) -> int:A sorted(A, keyabs, reverseTrue) # 将A按绝对值从大到小排列for i in range(len(A)):if K > 0 and A[i] < 0:A[i] * -1K - 1if K > 0:A[-1] *…

最基础的electron打包运行配置,以及Electron Builder 和 Electron Forge两者差异

node版本 v16.19.0 开发之前请务必去官网查看node所支持的版本&#xff0c;不然安装会报错 推荐使用 yarn 包管理 引入electron依赖 yarn add electron22.3.6 -D yarn add electron-builder -D创建electron配置文件 我这边为了模块化开发&#xff0c;将electron相关文件放…

Chapter3: Design Creation with RTL(ug949)

&#xff08;3.5&#xff09;Clock Domain Crossing 设计中的跨时钟域电路直接影响设计的可靠性。您可以设计自己的电路&#xff0c;但Vivado design Suite必须识别电路&#xff0c;并且必须正确应用ASYNC_REG属性。Xilinx提供XPM以确保正确的电路设计&#xff0c;包括…

【c/c++编译工具】——Cmake的学习

简介 目录 简介 1. Cmake的基本语法 2. 常用指令 3. CMake常用的变量 4. CMake编译工程 5. 构建方式 6. 实战---CMake代码实战 CMake是一个跨平台的安装编译工具&#xff0c;可以用简单的语句来描述所有平台的安装(编译过程)。CMake可以说已经成为大部分C开源项目标配…

将自己的服务注册成Windows服务

winsw下载地址 https://github.com/winsw/winsw/releases 如何注册服务 将WinSW.exe复制到自定义的目录同目录下创建projectName.xml。特别注意&#xff0c;xml和exe必须同名配置 xml文件 xml配置信息如下 <service><id>wjb-member</id><name>wjb-me…

15-721 Chapter7 索引

锁的分类 锁占用少量内存&#xff0c;实际上在不竞争的情况下性能不错. 第一个就是靠test_and_set实现的自旋锁&#xff0c;高效&#xff0c;因为在用户态&#xff0c;但是却不可扩展&#xff0c;对cache&#xff0c;os都不友好 第二个是mutex&#xff0c;实际上两个部分组成…

浮空,定位

浮动原理&#xff1a; 清除浮动 高度塌陷是怎么造成的&#xff0c;浮动元素脱离当前文档流&#xff0c;然后无法撑起父容器导致了高度塌陷&#xff0c;因为父容器没有设置高度 解决方法&#xff1a; 1.将父元素变成bfc元素 2.父元素浮动 3.清除浮动&#xff1a; 通过给父元…

小伙被内卷逼成扫地僧,把牛客网所有面试题整理成25W字面试手册

很多工作过了 5 年之后的工程师&#xff0c;都表示写不动 CRUD 代码了&#xff0c;都会考虑转架构师&#xff0c;但是一想到面试&#xff0c;可能心里就会一紧。 一般面试大厂架构师的岗位至少需要 3 轮技术面试&#xff0c;我咨询了一下阿里的大佬&#xff0c;了解到每一轮的…

数字化时代,如何建造会“运转”的数仓

在建设数仓之前需要根据数据基础和业务需求来决定要建设什么标准的数仓。 常见数仓问题 1、公共底层加工逻辑分散&#xff1a;对于来自多个数据源&#xff0c;但需要使用相同过滤和解析方式公共底层数仓&#xff0c;其过滤和解析代码在每个任务或配置中直接静态复制&#xff…

【系统集成项目管理工程师】项目范围管理

&#x1f4a5;十大知识领域&#xff1a;项目范围管理 项目范围管理包括以下 6 个过程: 编制范围管理计划过程收集需求过程定义范围过程创建工作分解结构过程确认范围过程范围控制过程 一、编制范围管理计划过程 对如何定义、确认和控制项目范围的过程进行描述 1、输出&#xff…

ch02-PyTorch数据预处理

ch02-PyTorch数据预处理0.引言1.数据读取机制 Dataloader 与 Dataset1.1.纸币二分类1.2.DataSet与DataLoader1.2.1.torch.utils.data.DataLoader&#xff1a;构建可迭代的数据装载器1.2.2.torch.utils.data.Dataset&#xff1a;Dataset抽象类1.2.3.以人民币分类为例2.数据预处理…

【Python】如何正确执行python装饰器?

文章目录前言一、错误的做法是什么&#xff1f;二、正确的方法总结前言 说到Python装饰器的执行顺序&#xff0c;有很多半吊子张口就来&#xff1a; 靠近函数名的装饰器先执行&#xff0c;远离函数名的装饰器后执行。 这种说法是不准确的。但是这些半吊子多半还会不服&#…

海明码的计算和检错纠错

海明码 1.学习前提 学习海明码之前&#xff0c;我们要约定3个原则&#xff1a; 海明码只能检测出2位错&#xff0c;纠1位错(因此不要问如果3位错怎么办等幼稚问题)。海明码默认进行偶校验(除非特殊说明使用奇校验)。海明码是一串由0和1组成的序列(除01外没有其他的值&#x…