一、动态Sql的使用
顾名思义,动态sql值得是事先无法预知具体条件,需要在运行时根据具体的情况动态生成Sql语句。例如:
id,create_time, name, password, phone, nick_name
上面的代码中,当我们不确定是否有查询条件时,可以使用
除了上面的几个标签外,mybatis的动态标签还有下面几个:
这组标签和java中的switch类似,when和otherwise条件都是互斥的 ,当任一when标签符合则其他标签不会走。
例如:
这种情况可以用
二、SqlSource 与 BoundSql详解
SqlSource用于描述sql资源,前面说过mybatis可以通过两种方式配置sql信息,一种通过注解(@Select等注解),一种通过xml(
该接口只有一个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是
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实现类的内容:
四、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方法完成对
其他的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