MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。
MyBatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑。总体说来 MyBatis 动态 SQL 语句主要有以下几类:
- if 语句 (简单条件判断)
- where (用来简化sql语句中where条件判断的,能智能的处理 and or,不必担心多余导致语法错误)
- set (主要用于更新时)
- trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)
- choose (when,otherwize) (相当于java 语言中的 switch ,与 jstl 中的choose 很类似)
- foreach (实现 mybatis in 语句查询时特别有用)
1. if 语句
动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。
首先看一个很普通的查询:
<!-- 查询作者列表,like作者名字 -->
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
SELECT * FROM author a
WHERE a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
AND a.mobile = #{mobile}
</select>
但是当 authorName 或 mobile 为 null 时,此语句很可能报错或查询结果为空。此时我们使用 if 动态 sql 语句先进行判断,如果值为 null 或等于空字符串,我们就不进行此条件的判断,增加灵活性。
<!-- 添加if(判断参数) - 将实体类 Author 不为空的属性作为 where 条件 -->
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
SELECT * FROM author a
WHERE
<if test="authorName != null">
a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
</if>
<if test="mobile != null">
AND a.mobile = #{mobile, jdbcType=VARCHAR}
</if>
</select>
这条语句提供了一个可选的文本查找类型的功能。
2. where 语句
上面的if语句,如果所有条件都没有匹配上将会怎样?最终这条 SQL 会变成这样:
SELECT * FROM author a
WHERE
这会导致查询失败。如果仅仅第二个条件匹配,最终SQL是这样:
SELECT * FROM author a
WHERE
AND a.mobile = #{mobile, jdbcType=VARCHAR}
这个查询也会失败。这个问题可以用一些方法解决,比如加一个永远为true的条件。
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
SELECT * FROM author a
WHERE 1=1
<if test="authorName != null">
AND a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
</if>
<if test="mobile != null">
AND a.mobile = #{mobile, jdbcType=VARCHAR}
</if>
</select>
MyBatis 有一个简单的处理就能得到想要的效果:
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
SELECT * FROM author a
<where>
<if test="authorName != null">
a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
</if>
<if test="mobile != null">
AND a.mobile = #{mobile, jdbcType=VARCHAR}
</if>
</where>
</select>
where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。
3. set 更新语句
当 update 语句中没有使用 if 标签时,如果有一个参数为 null,都会导致错误。
当在 update 语句中使用 if 标签时,如果前面的 if 没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。类似的用于动态更新语句的解决方案叫做 set。set 元素可以被用于动态包含需要更新的列,而舍去其他的。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。如下示例:
<!-- if/set(判断参数) - 将实体 Author 类不为空的属性更新 -->
<update id="updateAuthor" parameterType="Author">
UPDATE author
<set>
<if test="authorName != null and authorName != '' "> author_name = #{authorName}, </if>
<if test="mobile != null and mobile != '' "> mobile = #{mobile}, </if>
</set>
WHERE id = #{id};
</update>
set 标签元素主要是用在更新操作的时候,它的主要功能和 where 标签元素其实是差不多的,主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果 set 包含的内容为空的话则会出错。有了 set 元素就可以动态的更新那些修改了的字段。
4. trim 语句
如果 where 或 set 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
<trim prefix="SET" suffixOverrides=","> ... </trim>
prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除,并且插入 prefix 属性中指定的内容。
suffixOverrides属性会忽略后缀中的值。即suffixOverrides属性中指定的内容会被移除,并插入prefix 属性中指定的内容。
trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。
<select id="getAuthorListLikeName" parameterType="Author" resultMap="resultAuthorMap">
SELECT * FROM author a
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="authorName != null">
a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
</if>
<if test="mobile != null">
AND a.mobile = #{mobile, jdbcType=VARCHAR}
</if>
</trim>
</select>
<!-- if/set(判断参数) - 将实体 Author 类不为空的属性更新 -->
<update id="updateAuthor" parameterType="Author">
UPDATE author
<set>
<if test="authorName != null and authorName != '' "> author_name = #{authorName}, </if>
<if test="mobile != null and mobile != '' "> mobile = #{mobile}, </if>
</set>
WHERE id = #{id};
</update>
5. choose,when,otherwise语句
有时候我们并不想应用所有的条件,而只是想从多个选项中选择一二个。而使用if标签时,只要test中的表达式为 true,就会执行 if 标签中的条件。MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。if 标签是与(and)的关系,而 choose 是或(or)的关系。
choose 标签是按顺序判断其内部 when 标签中的 test 条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的 sql。类似于Java 的 switch 语句,choose 等同于 switch,when 等同于 case,otherwise 则等同于 default。
如下面的例子。choose 会从上到下选择一个 when 标签的 test 为 true 的 sql 执行。安全考虑,我们使用 where 将 choose 包起来。
<!-- choose(判断参数) - 按顺序将实体类 Author 第一个不为空的属性作为:where条件 -->
<select id="getAuthorList" resultMap="resultAuthorMap" parameterType="Author">
SELECT * FROM author a
<where>
<choose>
<when test="authorName != null">
a.author_name LIKE CONCAT(CONCAT('%', #{authorName}),'%')
</when>
<when test="mobile != null">
AND a.mobile = #{mobile, jdbcType=VARCHAR}
</when>
<otherwise>
a.mobile = "13666666666"
</otherwise>
</choose>
</where>
</select>
when元素表示当 when 中的条件满足的时候就输出其中的内容,跟 JAVA 中的 switch 效果差不多的是按照条件的顺序,当 when 中有条件满足的时候,就会跳出 choose,即所有的 when 和 otherwise 条件中,只有一个会输出,当所有的条件都不满足的时候就输出 otherwise 中的内容。
6. foreach
动态 SQL 的一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:
<select id="getAuthorListByIds" parameterType="java.util.List" resultMap="resultAuthorMap" >
SELECT * FROM author
WHERE id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<select id="getAuthorListByIds" parameterType="java.util.ArrayList" resultMap="resultAuthorMap">
SELECT * FROM author
WHERE id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<select id="dynamicForeach3Test" parameterType="java.util.HashMap" resultMap="resultAuthorMap">
SELECT * FROM author
WHERE id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
注意 你可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以“list”作为键,而数组实例的键将是“array”。