MyBatis 源码学习10——动态SQL实现原理(上)

一、动态SQL的使用

动态SQL:事先无法预知具体的条件,需要在运行时根据具体的情况动态地生成SQL语句。

使用MyBatis动态SQL进行条件查询的一个案例:

在这里插入图片描述

MyBatis动态SQL相关的标签

• <if>:通过OGNL表达式判断参数内容是否为空,如果表达式结果为true,则MyBatis框架会自动拼接标签内的SQL内容,否则会对标签内的SQL片段进行忽略

• <where>:用于保证至少有一个查询条件时,才会在SQL语句中追加WHERE关键字,同时能够剔除WHERE关键字后相邻的OR和AND关键字
• <choose|when|otherwise>:这几个标签需要组合使用,类似于Java中的switch语法:注意标签和标签是互斥的,当任何一个<when>标签满足条件时,其他标签均视为条件不成立。

• <foreach>:该标签用于对集合参数进行遍历,通常用于构建IN条件语句或者INSERT批量插入语句。

• <trim|set>:这两个标签的作用和标签的作用类似,用于WHERE子句中因为不同的条件成立时导致AND或OR关键字多余,或者SET子句中出现多余的逗号问题。

二、SqlSource与BoundSql详解

MyBatis中和SQL语句有关的两个组件,即SqlSource和BoundSql

SqlSource:描述SQL资源,MyBatis可有两种配置SQL信息的方式,一种是通过@Selelect、@Insert、@Delete、@Update或者@SelectProvider、@InsertProvider、@DeleteProvider、@UpdateProvider等注解;

一种是通过XML配置文件。SqlSource就代表Java注解或者XML文件配置的SQL资源。

SqlSource接口:在这里插入图片描述

只有一个getBoundSql()方法,返回一个BoundSql实例。

BoundSql:对SQL语句及参数信息的封装,它是SqlSource解析后的结果。

SqlSource接口有4个不同的实现,分别为StaticSqlSource、DynamicSqlSource、RawSqlSource和ProviderSqlSource:

ProviderSqlSource:用于描述通过@Select、@SelectProvider等注解配置的SQL资源信息。

• DynamicSqlSource:用于描述Mapper XML文件中配置的SQL资源信息,这些SQL通常包含动态SQL配置或者${}参数占位符,需要在Mapper调用时才能确定具体的SQL语句。

• RawSqlSource:用于描述Mapper XML文件中配置的SQL资源信息,与DynamicSqlSource不同的是,这些SQL语句在解析XML配置的时候就能确定,即不包含动态SQL相关配置。

• StaticSqlSource:用于描述ProviderSqlSource、DynamicSqlSource及RawSqlSource解析后得到的静态SQL资源。

无论是Java注解还是XML文件配置的SQL信息,在Mapper调用时都会根据用户传入的参数将Mapper配置转换为StaticSqlSource类。

了解一下StaticSqlSource类的实现:
在这里插入图片描述
StaticSqlSource类只封装了Mapper解析后的SQL内容和Mapper参数映射信息。

Executor组件与数据库交互,除了需要参数映射信息外,还需要参数信息。因此,Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的,而是与BoundSql交互。

BoundSql是对Executor组件执行SQL信息的封装,具体实现:
在这里插入图片描述

BoundSql除了封装了Mapper解析后的SQL语句和参数映射信息外,
还封装了Mapper调用时传入的参数对象。
另外,MyBatis任意一个Mapper都有两个内置的参数,即_parameter和_databaseId。
_parameter代表整个参数,包括标签绑定的参数信息,这些参数存放在BoundSql对象的additionalParameters属性中。
_databaseId为Mapper配置中通过databaseId属性指定的数据库类型。

三、LanguageDriver详解

LanguageDriver组件:完成SQL配置信息到SqlSource对象的转换
LanguageDriver接口:

在这里插入图片描述

一共有3个方法,
其中createParameterHandler()方法用于创建ParameterHandler对象,
另外还有两个重载的createSqlSource()方法,这两个重载的方法用于创建SqlSource对象。

LanguageDriver接口有两个实现类,分别为XMLLanguageDriverRawLanguageDriver

XMLLanguageDriver为XML语言驱动,为MyBatis提供了通过XML标签(我们常用的<if>、<where>等标签)结合OGNL表达式语法实现动态SQL的功能。

RawLanguageDriver表示仅支持静态SQL配置,不支持动态SQL功能。

重点了解一下XMLLanguageDriver类:
在这里插入图片描述

XMLLanguageDriver类实现了LanguageDriver接口中两个重载的createSqlSource()方法,分别用于处理XML文件和Java注解中配置的SQL信息,将SQL配置转换为SqlSource对象。

第一个重载的createSqlSource()方法用于处理XML文件中配置的SQL信息:
该方法中创建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode()方法将SQL资源转换为SqlSource对象。

第二个重载的createSqlSource()方法用于处理Java注解中配置的SQL信息:
在这里插入图片描述

1.首先判断SQL配置是否以<script>标签开头,如果是,则以XML方式处理Java注解中配置的SQL信息,

2.如果SQL中包含${}参数占位符,则SQL语句仍然需要根据传递的参数动态生成,使用DynamicSqlSource对象描述SQL资源,

3.否则说明SQL语句不需要根据参数动态生成,使用RawSqlSource对象描述SQL资源。

四、SqlNode详解

SqlNode:描述Mapper SQL配置中的SQL节点,它是MyBatis框架实现动态SQL的基石。

SqlNode接口:
在这里插入图片描述

SqlNode接口只有一个apply()方法,该方法用于解析SQL节点,根据参数信息生成静态SQL内容。

apply()方法需要接收一个DynamicContext对象作为参数,DynamicContext对象中封装了Mapper调用时传入的参数信息及MyBatis内置的_parameter和_databaseId参数。

每种标签都有其对映的SqlNode实现类:
在这里插入图片描述

• IfSqlNode:用于描述动态SQL中<if>标签的内容,XMLLanguageDriver在解析Mapper SQL配置生成SqlSource时,会对动态SQL中的标签进行解析,将<if>标签转换为IfSqlNode对象。‘

• ChooseSqlNode:用于描述动态SQL配置中的<choose>标签内容,Mapper解析时会把<choose>标签配置内容转换为ChooseSqlNode对象。

•ForEachSqlNode:用于描述动态SQL配置中的<foreach>标签,<foreach>标签配置信息在Mapper解析时会转换为ForEachSqlNode对象。

• MixedSqlNode:用于描述一组SqlNode对象,通常一个Mapper配置是由多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置。

• SetSqlNode:用于描述动态SQL配置中的<set>标签,Mapper解析时会把标签配置信息转换为SetSqlNode对象。

• WhereSqlNode:用于描述动态SQL中的<where>标签,动态SQL解析时,会把<where>标签内容转换为WhereSqlNode对象。

• TrimSqlNode:用于描述动态SQL中的<trim>标签,动态SQL解析时,会把<trim>标签内容转换为TrimSqlNode对象。在9.1节学习MyBatis动态SQL使用时,我们了解到<where>标签和<set>标签实际上是<trim>标签的一种特例,<where>标签和<set>标签实现的内容都可以使用<trim>标签来完成,因此WhereSqlNode和SetSqlNodel类设计为TrimSqlNode类的子类,属于特殊的TrimSqlNode。

• StaticTextSqlNode:用于描述动态SQL中的静态文本内容。

• TextSqlNode:该类与StaticTextSqlNode类不同的是,当静态文本中包含${}占位符时,说明${}需要在Mapper调用时将${}替换为具体的参数值。因此,使用TextSqlNode类来描述。

• VarDeclSqlNode:用于描述动态SQL中的<bind>标签,动态SQL解析时,会把标签配置信息转换为VarDeclSqlNode对象。

SqlNode与动态SQL配置之间的对应关系,例子:
在这里插入图片描述

上面是一个完整的Mapper SQL配置,该Mapper配置转换为SqlNode代码:
在这里插入图片描述

1.创建了一个StaticTextSqlNode和三个IfSqlNode来描述Mapper中动态SQL的配置,其中IfSqlNode由一个StaticTextSqlNode和条件表达式组成。

2.创建了一个MixedSqlNode将这些SqlNode组合起来,这样就完成了通过Java对象来描述动态SQL配置。

3.我们创建了一个DynamicContext对象,封装了Mapper调用时的参数信息。

4.将DynamicContext对象作为参数,调用MixedSqlNode的apply()方法根据参数内容动态地生成SQL内容了。

5.动态SQL的解析结果封装在DynamicContext对象中,调用DynamicContext对象的getSql()方法即可获取动态SQL解析后的SQL语句

SqlNode解析生成SQL语句的过程,先来看MixedSqlNode的实现
在这里插入图片描述
MixedSqlNode类通过一个List对象维护所有的SqlNode对象,
MixedSqlNode类的apply()方法中对所有SqlNode对象进行遍历,以当前DynamicContext对象作为参数,调用所有SqlNode对象的apply()方法。

接下来看一下StaticTextSqlNode的实现,代码如下:
在这里插入图片描述

StaticTextSqlNode实现类维护了Mapper配置中的静态SQL节点内容。
调用apply()方法时,将静态SQL文本内容追加到DynamicContext对象中。

最后了解一下IfSqlNode的实现
在这里插入图片描述

一个ExpressionEvaluator类的实例,该实例用于根据当前参数对象解析OGNL表达式。
一个属性test:维护了标签test属性指定的表达式内容
一个SqlNode类型的变量contents:存放标签中的SQL内容对应的SqlNode对象

apply()方法中,首先解析test属性指定的OGNL表达式,只有当表达式值为true的情况下,才会执行<if>标签中SQL内容对应的SqlNode的apply()方法。
这样就实现了只有当<if>标签test属性表达式值为true的情况下,才会追加<if>标签中配置的SQL信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值