动态 SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

     

虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。

     

动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

       

动态 SQL 通常要做的事情是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG 
  WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
</select>
       

这条语句提供了一种可选的查找文本功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会对“title”一列进行模糊查找并返回 BLOG 结果(细心的读者可能会发现,“title”参数值是可以包含一些掩码或通配符的)。

       

如果希望通过“title”和“author”两个参数进行可选搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

<select id="findActiveBlogLike"
     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 author_name like #{author.name}
  </if>
</select>

另一个示例,先来看看下面的代码:

<select id="dynamicIfTest" parameterType="Blog" resultType="Blog">  
        select * from t_blog where 1 = 1  
        <if test="title != null">  
            and title = #{title}  
        </if>  
        <if test="content != null">  
            and content = #{content}  
        </if>  
        <if test="owner != null">  
            and owner = #{owner}  
        </if>  
</select>  
        这条语句的意思非常简单,如果提供了 title 参数,那么就要满足 title=#{title},同样如果提供了 Content 和 Owner 的时候,它们也需要满足相应的条件,之后就是返回满足这些条件的所有 Blog,这是非常有用的一个功能,以往我们使用其他类型框架或者直接使用 JDBC 的时候, 如果我们要达到同样的选择效果的时候,我们就需要拼 SQL 语句,这是极其麻烦的,比起来,上述的动态SQL就比较简单了

choose, when, otherwise

       

有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<choose>、<when>和<otherwise>标签的使用必须符合以下语法规则:
    <when>和<otherwise>不能单独使用,它们必须位于<choose>父标签中。
    在<choose>标签中可以包含一个或多个<when>标签。
    在<choose>标签中可以不包含<otherwise>标签。
    在<choose>标签中如果同时包含<when>和<otherwise>标签,那么<otherwise>必须位于<when>标签之后。
   

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若两者都没有提供,就返回所有符合条件的 BLOG(实际情况可能是由管理员按一定策略选出 BLOG 列表,而不是返回大量无意义的随机结果)。

<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>

所以上述语句的意思非常简单, 当 title!=null 的时候就输出 AND title like #{title},不再往下判断条件,当title为空且author!=null和author.name!=null 的时候就输出 AND author_name like #{author.name}当所有条件都不满足的时候就输出 otherwise 中的内容。


trim, where, set

前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

<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>
</select>
       

如果这些条件没有一个能匹配上会发生什么?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE 
AND title like someTitle
       

这个查询也会失败。这个问题不能简单地用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不会再写出这种语句了。

       

MyBatis 有一个简单的处理,这在 90% 的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能达到目的:

<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 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

例子:

<select id="findWhereUser" parameterType="User" resultType="User">  
        select * from t_user  
        <where>  
            <if test="userName!= null">  
                user_name= #{userName}  
            </if>  
            <if test="sex!= null">  
                and sex = #{sex}  
            </if>  
        </where>  
</select>  
在 where 元素中你不需要考虑空格的问题,MyBatis 会自动的帮你加上。像上述例子中,如果 userName=null, 而 sex!= null,那么输出的整个语句会是 select * from t_blog where sex= #{sex},而不是 select * from t_user where and sex= #{sex},因为 MyBatis 会自动地把首个 and / or 给忽略。从而避免“WHERE AND”关键字多余的错误 SQL。
       

如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ... 
</trim>
       

prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。

       

类似的用于动态更新语句的解决方案叫做 setset 元素可以用于动态包含需要更新的列,而舍去其它的。比如:

<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 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。(译者注:因为用的是“if”元素,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留,会导致错误)

       

若你对 set 元素等价的自定义 trim 元素的代码感兴趣,那这就是它的真面目:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
        注意这里我们删去的是后缀值,同时添加了前缀值。

   trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;正因为 trim 有这样的功能,所以我们也可以非常简单的利用 trim 来代替 where 元素的功能。

  trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。

trim 代替 where

<!-- 使用 if/trim 代替 where(判断参数) - 将 User 类不为空的属性作为 where 条件 -->    
<select id="getUsertListTrim" resultMap="resultMap_User">    
    SELECT *   
      FROM user u  
    <trim prefix="WHERE" prefixOverrides="AND|OR">    
        <if test="userName !=null ">    
            u.username LIKE CONCAT(CONCAT('%', #{userName, jdbcType=VARCHAR}),'%')    
        </if>    
        <if test="sex != null and sex != '' ">    
            AND u.sex = #{sex, jdbcType=INTEGER}    
        </if>    
    </trim>       
</select>   
trim 代替 set
<!-- if/trim代替set(判断参数) - 将 User 类不为空的属性更新 -->    
<update id="updateUser_if_trim" parameterType="User">    
    UPDATE user    
    <trim prefix="SET" suffixOverrides=",">    
        <if test="userName != null and userName != '' ">    
            user_name = #{userName},    
        </if>    
        <if test="sex != null and sex != '' ">    
            sex = #{sex},    
        </if>    
    </trim>    
    WHERE user_id = #{user_id}    
</update>   


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)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

       

注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。


下面分别来看看上述三种情况的示例代码:

1.单参数List的类型:

<select id="findForeachUser" resultType="User">  
        select * from t_user where id in  
        <foreach collection="list" index="index" item="id" open="(" separator="," close=")"   >  
            #{item}  
        </foreach>  
</select>  
上述collection的值为list,对应的Mapper是这样的
public List<User> findForeachUser(List<Integer> ids);  
测试代码:
@Test  
    public void findForeachTest() {  
        SqlSession session = Util.getSqlSessionFactory().openSession();  
        UserMapper userMapper = session.getMapper(userMapper.class);  
        List<Integer> ids = new ArrayList<Integer>();  
        ids.add(1);  
        ids.add(3);  
        ids.add(6);  
        List<User> users = userMapper.findForeachUser(ids);  
        for (User user: users)  
            System.out.println(user.toString());  
        session.close();  
    }  
2.单参数array数组的类型:
<select id="findForeachUser" resultType="User">  
        select * from t_user where id in  
        <foreach collection="array" index="index" item="id" open="(" separator="," close=")">  
            #{id}  
        </foreach>  
</select>  
上述collection为array,对应的Mapper代码:
public List<User> findForeachUser(int[] ids);  
测试代码:
@Test  
   public void findForeachTest() {  
       SqlSession session = Util.getSqlSessionFactory().openSession();  
       UserMapper userMapper = session.getMapper(UserMapper.class);  
       int[] ids = new int[] {1,3,6,9};  
       List<User> users = userMapper.findForeachUser(ids);  
       for (User user: users)  
           System.out.println(user);  
       session.close();  
   }  
3.自己把参数封装成Map的类型

<select id="findForeachUser" resultType="User">  
        select * from t_user where user_name like "%"#{userName}"%" and id in  
        <foreach collection="ids" index="index" item="id" open="(" separator="," close=")">  
            #{id}  
        </foreach>  
</select>  
上述collection的值为ids,是传入的参数Map的key,对应的Mapper代码:
public List<User> findForeachUser(Map<String, Object> params);  
测试代码:
@Test  
    public void findForeachUserTest() {  
        SqlSession session = Util.getSqlSessionFactory().openSession();  
        UserMapper userMapper = session.getMapper(UserMapper.class);  
        final List<Integer> ids = new ArrayList<Integer>();  
        ids.add(1);  
        ids.add(2);  
        ids.add(3);  
        Map<String, Object> params = new HashMap<String, Object>();  
        params.put("ids", ids);  
        params.put("userName", "落尘曦");  
        List<User> users= userMapper.findForeachUser(params);  
        for (User user: users)  
            System.out.println(user);  
        session.close();  
    }  

到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。

猜你喜欢

转载自blog.csdn.net/q343509740/article/details/80582725
今日推荐