MyBatis 最强大的特性之一就是它的动态语句功能。如果您以前有使用 JDBC 或者类似框架的经历,您就会明白把 SQL 语句条件连接在一起是多么的痛苦,要确保不能忘记空格或者不要在 columns 列后面省略一个逗号等。动态语句能够完全解决掉这些痛苦。
尽管与动态 SQL 一起工作不是在开一个 party,但是 MyBatis 确实能通过在任何映射 SQL 语句中使用强大的动态 SQL 来改进这些状况。
动态 SQL 元素对于任何使用过 JSTL 或者类似于 XML 之类的文本处理器的人来说,都是非常熟悉的。在上一版本中,需要了解和学习非常多的元素,但在 MyBatis3 中有了许多的改进,现在只剩下差不多二分之一的元素。MyBatis 使用了基于强大的 OGNL 表达式来消除了大部分元素。
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
一、if 元素
动态 SQL 最常做的事就是有条件地包括 where 子句。例如:
<select id="findActiveBlogWithTitleLike" parameterType="Blog" resultType="Blog"> SELECT * FROM BLOG WHERE state='ACTIVE' <if test="title!=null"> AND title like #{title} </if> </select>
这条语句提供一个带功能性的可选的文字。如果您没有传入标题,那么将返回所有激活的 Blog。如果您传入了一个标题,那它就会查找与这个标题匹配的 Blog(在这种情况下,您的参数值可能需要包括任何 masking 或者通配符)。
如果我们想要可选地根据标题或者作者查询怎么办?首先,我把语句的名称稍稍改一下,使得看起来更直观。然后简单地加上另外一个条件。
<select id="findActiveBlogLike" parameterType="Blog" resultType="Blog"> SELECT * FROM BLOG WHERE state='ACTIVE' <if test="title!=null"> AND title like #{title} </if> <if test="author!=null and author.name!=null"> AND title like #{author.name} </if> </select>
二、choose,when,otherwise元素
有时候我们不想应用所有的条件,而是想从多个选项中选择一个。与 java 中的 switch 语句相似,MyBatis 提供了一个 choose 元素。
让我们继续使用上面的例子,但这次我们只搜索有提供查询标题的,或者只搜索有提供查询作者的数据。如果两者都没有提供,那只返回加精的 Blog(可能是管理员有选择性的查询,而不是返回大量无意义的随机的 Blog)。
<select id="findActiveBlogLike" parameterType="Blog" 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 title like #{author.name} </when> <otherwise> AND featured=1 </otherwise> </choose> </select>
三、trim,where,set元素
考虑一下我们上面提到的 'if' 的例子中,如果现在我们把'ACTIVE=1'也做为条件,会发生什么情况。
<select id="findActiveBlogLike" parameterType="Blog" 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 title like#{author.name} </if> </select>
如果我们一个条件都不设置,会发生什么呢?语句最终可能会变成这个样子:
SELECT * FROM BLOG
WHERE
这将会执行失败。如果只有第二个条件满足呢?语句最终会变成这样:
SELECT * FROM BLOG
WHERE
AND title like 'someTitle'
这同样会执行失败。这个问题仅用条件很难简单地解决,如果您已经这么写了,那您可能以后永远都不想犯这样的错了。
MyBatis 有个简单的方案能解决这里面 90% 的问题。如果 where 没有出现的时候,您可以自定一个。修改一下,就能完全解决:
<select id="findActiveBlogLike" parameterType="Blog" 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 title like #{author.name} </if> </where> </select>
where 元素知道插入 "where" 如果它包含的标签中有内容返回的话。此外,如果返回的内容以 "AND" 或者 "OR" 开头,它会把 "AND" 或者 "OR" 去掉。
如果 where 元素的行为并没有完全按您想象的那样,您还可以使用 trim 元素来自定义。例如,下面的 trim 与 where 元素实现相同功能:
<trim prefix="WHERE" prefixOverrides="AND|OR"> … </trim>
overrides 属性使用了管道分隔的文本列表来覆写,而且它的空白也不能忽略的。这样的结果是移出了指定在 overrides 属性里字符,而在开头插入 prefix 属性中指定的字符。
下面的两种配置方法效果是一样的:
<selectid="findActiveBlogLike" parameterType="Blog" 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 title like #{author.name} </if> </where> </select> <selectid="findActiveBlogLike" parameterType="Blog" resultType="Blog"> SELECT * FROM BLOG <trim prefix="WHERE" prefixOverrides="AND |OR"> <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 title like #{author.name} </if> </trim> </select>
下面的使用 SET 元素也类似。
在动态 update 语句里相似的解决方式叫做 set,这个 set 元素能够动态地更新列。例如:
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author"> 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> whereid=#{id} </update>
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>
对上面这个映射 SQL 语句的 java 调用代码示例如下:
List<Integer> authorIdList = new ArrayList<Integer>(); postList.add(2); postList.add(3); postList.add(4); List<Post> postList =(List<Post>)session.selectList("selectPostIn",postList); //将会查询出ID是2、3、4的文章。
foreach 元素非常强大,允许您指定一个集合,申明能够用在元素体内的项和索引变量。也允许您指定开始和结束的字符,也可以加入一个分隔符到迭代器之间。这个元素的聪明之处在于它不会意外地追加额外的分隔符。
注意:您可以把一个 List 实例或者一个数组作为一个参数对象传递给 MyBatis。如果您这么做,MyBatis 会自动将它包装成一个 Map,并以名字作为 key。List 实例会以 "list" 作为key,array 实例会以 "array" 作为key。