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)"></</span><span style="color: rgba(128, 0, 0, 1)">select</span><span style="color: rgba(0, 0, 255, 1)">></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><Product> 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><Integer> ids = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList<Integer><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><Product> 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();
}
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<Integer><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<Integer><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<Integer><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; }
}
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>