Mybatis学习系列(三)动态SQL

  在 mapper 配置文件中,有时需要根据查询条件选择不同的 SQL 语句,或者将一些使用频率高的 SQL 语句单独配置,在需要使用的地方引用。Mybatis 的一个特性: 动态 SQL,来解决这个问题。

mybatis 动态 sql 语句是基于 OGNL 表达式的,主要有以下几类:

1. if 语句 (简单的条件判断)

2. choose (when,otherwize) , 相当于 java 语言中的 switch , 与 jstl 中的 choose 很类似

3. trim (对包含的内容加上 prefix, 或者 suffix 等,前缀,后缀)

4. where (主要是用来简化 sql 语句中 where 条件判断的,能智能的处理 and or , 不必担心多余导致语法错误)、

5. set (主要用于更新时)

6. foreach (在实现 mybatis in 语句查询时特别有用)

if 标签语句

  if 标签用来实现根据条件拼接 sql 语句,下面示例用来判断参数如果不为 null,则拼接 sql

示例:

 <select id="ifTest" resultType="com.sl.po.Product">
        select * from products where 
        <if test="ProductName!=null">
            name like #{ProductName}
        </if>
        <if test="description!=null">
            and description like CONCAT(CONCAT('%', #{Description, jdbcType=VARCHAR}),'%')
        </if>
    </select>

当参数 ProductName 和 Description 不为 null, 则正常拼接处 sql 语句:select * from products where name like ? and description like CONCAT(CONCAT('%', ?),'%')

但是上面的 SQL 中如果传入的参数 ProductName 为 null, 则解析出错误的语句:select * from products where and description like CONCAT(CONCAT('%', ?),'%') ,解决这个问题,需要用到 where 标签

where 标签语句

  当 where 中的条件使用的 if 标签较多时,这样的组合可能会导致错误, “where”标签会自动判断如果它包含的标签中有返回值的话,就在 sql 中插入一个‘where’,如果 where 标签最后返回的内容是以 and 或者 or 开头的,也会被自动移除掉。

上面的示例用 where 标签改写后示例如下:

<select id="whereTest" resultType="com.sl.po.Product">
        select * from products
<!-- where 标签自动移除第一个 and-->
<where> <if test="Name!=null"> and name like #{Name} <!--name like #{Name}--> </if> <if test="description!=null"> and description like #{Description} </if> </where> </select>

set 标签语句

  set 标签是用在更新操作的时候,功能和 where 标签元素差不多,主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果 set 标签最终返回的内容为空的话则可能会出错(update table where id=1)

 使用 set 标签示例:

<!-- if + set 实现按条件更新-->
    <update id="setTest">
        update products
<!-- set 标签将移除最后一个“,” -->
<set> <if test="cityCode!=null"> citycode = #{cityCode} , </if> <if test="Name!=null"> name = #{Name} , </if> <if test="description!=null"> description = #{Description} , </if> </set> where id =#{id} </update>

trim 标签语句

  trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;正因为 trim 有这样的功能,它可以用来实现 where 和 set 的效果。

前面 where 标签示例,此处使用 trim 代替:

<!-- if+trim 使用 trim 代理 where-->
    <select id="trimwhereTest" resultType="com.sl.po.Product">
        select * from products
       <!--
          <where>
            <if test="Name!=null">
                and name like #{Name}
                <!--name like #{Name}-->
            </if>
            <if test="description!=null">
                and description like #{Description}
            </if>
        </where>
       -->
<!-- 移除首部所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容 -->
<trim prefix="WHERE" prefixOverrides="AND |OR"> <if test="Name!=null"> and name like #{Name} </if> <if test="description!=null"> and description like #{Description} </if> </trim>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">select</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>

前面 set 标签示例,此处使用 trim 代替:

<!--if+trim 代替 使用 trime 代替 set  -->
    <update id="trimsetTest">
      update products
    <!--
     <set>
            <if test="cityCode!=null">
              citycode = #{cityCode} ,
            </if>
            <if test="Name!=null">
               name = #{Name} ,
            </if>
            <if test="description!=null">
                description = #{Description}
            </if>
        </set>
   -->
 <!-- 移除尾部所有指定在 suffixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容 -->
      <trim prefix="set" suffixOverrides=",">
            <if test="cityCode!=null and cityCode!=''">
              citycode = #{cityCode} ,
            </if>
            <if test="Name!=null">
               name = #{Name} ,
            </if>
            <if test="description!=null">
                description = #{Description}
            </if>
        </trim>
        where id=#{id}
    </update> 

choose (when, otherwise) 标签

  choose 标签是按顺序判断其内部 when 标签中的 test 条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的 sql,类似于 sql server 语句(case when then)

示例:

<!-- choose + when + otherwise 只能选择一个作为查询条件 作用类似 sql case when then -->
    <select id="choosewhenotherwiseTest" resultType="com.sl.po.Product">
        select * from products
     <where>
        <choose>
            <when test="name!=null">
                and name like #{Name}
            </when>
            <when test="description!=null">
                and description like #{Description}
            </when>
            <otherwise>
                and unitprice > #{UnitPrice}
            </otherwise>
        </choose>
     </where>
   </select>

如果 name!=null,则解析出 sql: select * from product where name like ?

Name==null&& description!=null, 则解析出 sql: select * from product where description like ?

否则:select * from product where unitprice >?

foreach 标签语句

  mybatis 提供 foreach 标签,用来对一个集合进行遍历,通常是用来构建 IN 条件语句,也可用于其他情况下动态拼接 sql 语句。

foreach 标签有以下几个属性 collection, item,index,open,separator,close。

1. collection 表示需要遍历的集合

2. item 表示每次遍历时生成的对象名

3. index 表示在迭代过程中,每次迭代到的位置 )

4. open 表示开始遍历时要拼接的字符串

5. separator 表示在每次遍历时两个对象直接的连接字符串

6. close 表示结束遍历时要拼接的字符串

当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

在使用 foreach 的时候针对不同的参数类型, collection 属性值要分为以下 3 种情况:

1. 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list

2. 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array

3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个 Map 或者 Object。

 示例:

<!-- 只有一个 List 参数时它的参数名为 list,即 collection="list" ;  如果参数类型时数组 object[], 则  collection="array" -->
<select id="foreachTest" resultType="com.sl.po.Product">
      select * from products 
      <where>
        <if test="list!=null">
          <foreach item="id" index="index"  collection="list" open="id in(" separator="," close=")">#{id}</foreach>
        </if>
      </where>
</select>
<!-- 通过 pojo 传递 list, collection 值为 pojo 中对应的属性名-->
<select id="foreachVoTest" resultType="com.sl.po.Product">
      select * from products 
      <where>
         <if test="name!=null"> and name like #{name} </if>
         <if test="ids!=null">
            <foreach item="item" index="index"  collection="ids" open="and id in(" separator="," close=")">#{item}</foreach>
         </if>
      </where>
 </select>

测试代码:

//@Test
        public void testforeachTest() {
            String statement = "com.sl.mapper.ProductMapper.foreachTest";
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
        List</span>&lt;Product&gt; listProduct =<span style="color: rgba(0, 0, 0, 1)"> session.selectList(statement, list);
        </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Product pro : listProduct) {
            System.out.println(pro);
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭会话</span>

session.close();
}

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">@Test</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> testforeachVoTest() {
        String statement </span>= "com.sl.mapper.ProductMapper.foreachVoTest"<span style="color: rgba(0, 0, 0, 1)">;
        ProductVo2 vo2 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ProductVo2();
        vo2.setName(</span>"%国际%"<span style="color: rgba(0, 0, 0, 1)">);
        List</span>&lt;Integer&gt; ids = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList&lt;Integer&gt;<span style="color: rgba(0, 0, 0, 1)">();
        ids.add(</span>11<span style="color: rgba(0, 0, 0, 1)">);
        ids.add(</span>12<span style="color: rgba(0, 0, 0, 1)">);
        ids.add(</span>13<span style="color: rgba(0, 0, 0, 1)">);
        vo2.setIds(ids);
        
        List</span>&lt;Product&gt; listProduct =<span style="color: rgba(0, 0, 0, 1)"> session.selectList(statement, vo2);
        </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Product pro : listProduct) {
            System.out.println(pro);
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭会话</span>

session.close();
}

View Code
package com.sl.po;

import java.util.List;

public class ProductVo2 {

</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String name;

</span><span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;Integer&gt;<span style="color: rgba(0, 0, 0, 1)"> ids;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getName() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setName(String name) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> List&lt;Integer&gt;<span style="color: rgba(0, 0, 0, 1)"> getIds() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ids;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setIds(List&lt;Integer&gt;<span style="color: rgba(0, 0, 0, 1)"> ids) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.ids =<span style="color: rgba(0, 0, 0, 1)"> ids;
}

}

View Code

Sql 片段

  Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的

示例:

<select id="sqlTest" resultType="com.sl.po.Product">
        select * from products
        <where>
<!-- 引用 sql 片段 --> <include refid="sqltemp"/> <!-- 提取 sql 片段 <if test="cityCode!=null"> and citycode = #{cityCode} </if> <if test="Name!=null"> and name like #{Name} </if> <if test="description!=null"> and description like #{Description} </if> --> </where> </select>
<!-- 定义 sql 片段 :将 where 条件提取 --> <sql id="sqltemp"> <if test="cityCode!=null"> and citycode = #{cityCode} </if> <if test="Name!=null"> and name like #{Name} </if> <if test="description!=null"> and description like #{Description} </if> </sql>