MyBatis动态SQL
动态SQL是MyBatis重要特性之一。
动态SQL类型
if
最常见的场景就是根据条件包含where子句的一部分。
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title = #{title}
</if>
</select>
choose,when,otherwise
不想使用所有条件,而是中多个条件中选择一个。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim,where,set
如果不匹配
SELECT * FROM BLOG
WHERE
如果只匹配第二个条件
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
都会导致查询失败。可以使用以下方式避免。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素只会在⼦元素返回任何内容的情况下才插⼊ “WHERE” ⼦句。⽽且,若⼦句的开头为”AND”或”OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太⼀样,可以通过⾃定义 trim 元素来定制 where 元素的功能。⽐如,和 where 元素等价的⾃定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的⽂本序列(注意此例中的空格是必要的)。上述例⼦会移除所有 prefixOverrides 属性中指定的内容,并且插⼊ prefix 属性中指定的内容。
⽤于动态更新语句的类似解决⽅案叫做 set。set 元素可以⽤于动态包含需要更新的列,忽略其它不更新的列。
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例⼦中,set 元素会动态地在⾏⾸插⼊ SET 关键字,并会删掉额外的逗号(这些逗号是在使⽤条件语句给列赋值时引⼊的)。与set元素等价的自定义trim元素:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
覆盖了后缀值设置,并且⾃定义了前缀值。
foreach
对集合进行遍历构建SQL语句(尤其IN条件语句时)。
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能⾮常强⼤,允许指定⼀个集合,声明可以在元素体内使⽤的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。
动态执行原理
SqlResource
:sql对象的来源,通过该接口可以获取sql对象。其唯一实现类是XmlSqlResource
,表示通过xml文件生成sql对象。Sql
:生成sql语句和获取sql相关的上下文环境(如ParameterMap,ResultMap等),三个实现类RawSql
:原生sql语句,在初始化即可确定的sql语句SimpleDynamicSql
:简单的动态sql,即sql语句中参数通过$property$
方式指定,参数在sql生成过程会被替换,不作为sql执行参数DynamicSql
:动态sql,即sql描述文件中包含isNotNull,isGreaterThan等条件标签
SqlChild
:表示sql抽象语法树的一个节点,包含sql语句的片段信息。两个实现类。每条动态sql通过SqlTag和SqlText构成相应的抽象语法树。SqlTag
:表示动态sql片段,即配置文件中有一个动态标签,内含动态sql属性值(如prepend、property值等)。SqlText
:表示静态sql⽚段,即为原⽣的sql语句。
SqlTagHandler
:该接⼝表示SqlTag(即不同的动态标签)对应的处理⽅式。⽐如实现类IsEmptyTagHandler
⽤于处理isEmpty
标签,IsEqualTagHandler
⽤于处理isEqual
标签。SqlTagContext
:⽤于解释sql抽象语法树时使⽤的上下⽂环境。通过解释语法树每个节点,将⽣成的sql存⼊SqlTagContext。最终通过SqlTagContext获取完整的sql语句。
总结
涉及三个模式
- 解析器模式:初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,⽣成sql语句。
- 工厂模式:为动态标签的处理⽅式创建⼯⼚类(SqlTagHandlerFactory),根据标签名称获取对应的处理⽅式。
- 策略模式:将动态标签处理⽅式抽象为接⼝,针对不同标签有相应的实现类。解释抽象语法树时,定义统⼀的解释流程,再调⽤标签对应的处理⽅式完成解释中的各个⼦环节。