动态SQL:事先无法预知具体的条件,需要在运行时根据具体的情况动态地生成SQL语句。
使用MyBatis动态SQL进行条件查询的一个案例:
MyBatis动态SQL相关的标签:
•
:通过OGNL表达式判断参数内容是否为空,如果表达式结果为true,则MyBatis框架会自动拼接标签内的SQL内容,否则会对标签内的SQL片段进行忽略
•
:用于保证至少有一个查询条件时,才会在SQL语句中追加WHERE关键字,同时能够剔除WHERE关键字后相邻的OR和AND关键字
•
标签满足条件时,其他标签均视为条件不成立。
•
:该标签用于对集合参数进行遍历,通常用于构建IN条件语句或者INSERT批量插入语句。
•
:这两个标签的作用和标签的作用类似,用于WHERE子句中因为不同的条件成立时导致AND或OR关键字多余,或者SET子句中出现多余的逗号问题。
MyBatis中和SQL语句有关的两个组件,即SqlSource和BoundSql。
SqlSource:描述SQL资源,MyBatis可有两种配置SQL信息的方式,一种是通过@Selelect、@Insert、@Delete、@Update或者@SelectProvider、@InsertProvider、@DeleteProvider、@UpdateProvider等注解;
一种是通过XML配置文件。SqlSource就代表Java注解或者XML文件配置的SQL资源。
只有一个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组件:完成SQL配置信息到SqlSource对象的转换
LanguageDriver接口:
一共有3个方法,
其中createParameterHandler()方法用于创建ParameterHandler对象,
另外还有两个重载的createSqlSource()方法,这两个重载的方法用于创建SqlSource对象。
LanguageDriver接口有两个实现类,分别为XMLLanguageDriver和RawLanguageDriver。
XMLLanguageDriver为XML语言驱动,为MyBatis提供了通过XML标签(我们常用的
等标签)结合OGNL表达式语法实现动态SQL的功能。
RawLanguageDriver表示仅支持静态SQL配置,不支持动态SQL功能。
XMLLanguageDriver类实现了LanguageDriver接口中两个重载的createSqlSource()方法,分别用于处理XML文件和Java注解中配置的SQL信息,将SQL配置转换为SqlSource对象。
第一个重载的createSqlSource()方法用于处理XML文件中配置的SQL信息:
该方法中创建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode()方法将SQL资源转换为SqlSource对象。
第二个重载的createSqlSource()方法用于处理Java注解中配置的SQL信息:
1.首先判断SQL配置是否以标签开头,如果是,则以XML方式处理Java注解中配置的SQL信息,
2.如果SQL中包含${}参数占位符,则SQL语句仍然需要根据传递的参数动态生成,使用DynamicSqlSource对象描述SQL资源,
3.否则说明SQL语句不需要根据参数动态生成,使用RawSqlSource对象描述SQL资源。
SqlNode:描述Mapper SQL配置中的SQL节点,它是MyBatis框架实现动态SQL的基石。
SqlNode接口只有一个apply()方法,该方法用于解析SQL节点,根据参数信息生成静态SQL内容。
apply()方法需要接收一个DynamicContext对象作为参数,DynamicContext对象中封装了Mapper调用时传入的参数信息及MyBatis内置的_parameter和_databaseId参数。
• IfSqlNode:用于描述动态SQL中
标签的内容,XMLLanguageDriver在解析Mapper SQL配置生成SqlSource时,会对动态SQL中的标签进行解析,将
标签转换为IfSqlNode对象。‘
• ChooseSqlNode:用于描述动态SQL配置中的
标签内容,Mapper解析时会把
标签配置内容转换为ChooseSqlNode对象。
•ForEachSqlNode:用于描述动态SQL配置中的
标签,
标签配置信息在Mapper解析时会转换为ForEachSqlNode对象。
• MixedSqlNode:用于描述一组SqlNode对象,通常一个Mapper配置是由多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置。
• SetSqlNode:用于描述动态SQL配置中的
标签,Mapper解析时会把标签配置信息转换为SetSqlNode对象。
• WhereSqlNode:用于描述动态SQL中的
标签,动态SQL解析时,会把
标签内容转换为WhereSqlNode对象。
• TrimSqlNode:用于描述动态SQL中的
标签,动态SQL解析时,会把
标签内容转换为TrimSqlNode对象。在9.1节学习MyBatis动态SQL使用时,我们了解到
标签和
标签实际上是
标签的一种特例,
标签和
标签实现的内容都可以使用
标签来完成,因此WhereSqlNode和SetSqlNodel类设计为TrimSqlNode类的子类,属于特殊的TrimSqlNode。
• StaticTextSqlNode:用于描述动态SQL中的静态文本内容。
• TextSqlNode:该类与StaticTextSqlNode类不同的是,当静态文本中包含${}
占位符时,说明${}
需要在Mapper调用时将${}
替换为具体的参数值。因此,使用TextSqlNode类来描述。
• VarDeclSqlNode:用于描述动态SQL中的
标签,动态SQL解析时,会把标签配置信息转换为VarDeclSqlNode对象。
上面是一个完整的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对象中。
一个ExpressionEvaluator类的实例,该实例用于根据当前参数对象解析OGNL表达式。
一个属性test:维护了标签test属性指定的表达式内容
一个SqlNode类型的变量contents:存放标签中的SQL内容对应的SqlNode对象
apply()方法中,首先解析test属性指定的OGNL表达式,只有当表达式值为true的情况下,才会执行
标签中SQL内容对应的SqlNode的apply()方法。
这样就实现了只有当
标签test属性表达式值为true的情况下,才会追加
标签中配置的SQL信息。