mybatis 的动态SQL

在XML 中支持的几种标签:  • if  • choose、when、otherwise  • where  • set  trim   • foreach


OGNL 表达式

1. el or e2
2. el and e2
3. el == e2 或 el eq e2
4. el != e2 或 el neq e2
5. el lt e2 :小于
6. el lte e2 :小于等于,其他表示为gt(大于)、gte(大于等于)
7. el + e2 、el - e2 、e1 * e2 、e 1 / e2 、el % e2
8. !e 或 not e :非,取反
9. e.method(args) : 调用对象方法
JO. e.property: 对象属性值
11. el[e2]:按索引取值(List、数组和Map)
12. @class@method(args):调用类的静态方法
13. @class@field:调用类的静态字段值
map['userName']或map.userName来获取map中key为userName的值。

< if test= "@tk.mybatis.util.StringUtil@isNotEmpty(userName)">
  and user_name like concat('%', #{userName}, '%')
</if>
其中StringUtil 类如下:
public class StringUtil {
  public static boolean isEmpty(String str) {
    return str == null || str.length() == 0;
  }

  public static boolean isNotEmpty(String str) {
   return !isEmpty(str) ;
  }
}


if 标签有一个必填的属性test, test 的属性值是一个符合OGNL 要求的判断表达式,表达式的结果可以是true 或false ,除此之外所有的非0 值都为true ,只有0 为false。

条件查询
1、判断条件property != null 或property == null,适用于任何类型的字段,用于判断属性值是否为空。
2、判断条件property != '' 或 property == '', 仅适用于String 类型的宇段,用于判断是否为空字符串。
3、判断条件list != null and list.size()>0,判断一个集合是否为空。
4、当有多个判断条件时,使用and 或or 进行连接,嵌套的判断可以使用小括号分组,and 相当于Java 中的与(&&),or 相当于Java 中的或(||)
条件查询
<select >
  and user_email = #{userEmail}
</if>
</select>
1 = 1 有两个作用,作用1:防止报错;作用2:全表查询。

条件更新
现在要实现这样一个需求,只更新有变化的字段。更新的时候不能将原来有值但没有发生变化的字段更新为空或null。通过if标签可以实现这种动态列更新。
<update >
  user_email = #{userEmail},
</if>
  id = #{id}
where id = #{id}
</update>
注意:where 关键字前面的id = #{id},防止报错。
1、如果全部条件都为空或null,sql就是 --> update sys_user set where id = #{id}。
2、查询条件只有一个不是null 也不是空(假设是userName),sql就是 --> update sys_user set user_name= #{userName}, where id= #{id}

条件插入
在数据库表中插入数据的时候,如果某一列的参数值不为空,就使用传入的值,如果传入参数为空,就使用数据库中的默认值,而不使用传入的空值。使用if也可以实现这种动态插入列的功能。
<insert >
  #{userEmail},
</if>
#{userInfo}, #{headImg,jdbcType=BLOB},
#{createTime, jdbcType = TIMESTAMP})
</insert>


if标签提供了基本的条件判断,但是它无法实现if...else if...else...的逻辑,要想实现这样的逻辑,就需要用到choose when otherwise标签。choose元素中包含when和otherwise两个标签,一个choose中至少有一个when,有0个或者l个otherwise。

choose when when when 是if...else if...else if的关系,只会走其中的一条路,假如第一个when返回true,那么即使第二个第三个都为true,条件里也不会有第二个和第三个。
choose when 和下面的where 相比,没有剔除第一个条件前面的and或or的功能。
<select id= "" resultType="test.mybatis.simple.model.SysUser">
select id , user_name, user_password , user_email, user_info, head_img, create_time
from sys_user
where 1 = 1
<choose>
  <when test = "id != null">
    and id= #{id}
  </when>
  <when test= "userName != null and userName !='' ">
    and user_name = #{userName}
  </when>
  <otherwise>
    and 1 = 2
  </otherwise>
</choose>
</select>


where标签的作用:

1、当if条件都不满足的时候,where元素中没有内容,SQL中不会出现where;
2、紧跟在where元素后面的第一个查询条件前面如果以and 或 or开头,那么剔除;
3、where 标签,或where if 标签组合,没有给条件前面添加and 或 or 的功能,所以and或or一定要自己手动添加。


set标签的作用:

1、如果该标签包含的元素中有内容,就插入一个set,如果set元素中没有内容,不插入set标签,出现SQL错误,所以为了避免错误产生,类似id=#{id}这样必然存在的赋值仍然有保留的必要。从这一点来看,set标签并没有解决全部的问题,使用时仍然需要注意。
2、如果最后一个条件以逗号结尾,就将这个逗号剔除;其它条件的逗号不会加也不会减。


trim 标签

where和set标签的功能都可以用trim标签来实现,并且在底层就是通过TrimSqlNode实现的。
where标签对应trim的实现如下:
<trim prefix="WHERE" prefixOverrides="AND |OR " >
...
</trim>
这里的AND和OR后面的空格不能省略,为了避免匹配到andes、orders等单词。实际的prefixeOverrides 包含"AND"、"OR"、"AND "、"OR "、"AND "、"OR "、"AND "、"OR ",不仅仅是上面提到的两个带空格的前缀。
set标签对应的trim实现如下。
<trim prefix="SET" suffixOverrides="," >
...
</trim>
trim 标签有如下属性。
prefix:当trim 元素内包含内容时,会给内容增加prefix 指定的前缀。
suffix:当trim 元素内包含内容时,会给内容增加suffix 指定的后缀。
prefixOverrides:当trim 元素内包含内容时,会把内容中匹配的前缀字符串去掉。
suffixOverrides:当trim 元素内包含内容时,会把内容中匹配的后缀字符串去掉。


foreach

foreach可以对数组、Map或实现了iterable接口(如List、Set)的对象进行遍历。数组在处理时会转换为List对象,因此foreach遍历的对象可以分为两大类:Iterable类型和Map类型。
collection:必填,值为要迭代循环的属性名。这个属性值的情况有很多。
item:变量名,值为从迭代对象中取出的每一个值,当迭代的对象是Map类型时,这个值为Map的value
index:索引的属性名,在集合数组情况下值为当前索引值,当迭代的对象是Map类型时,这个值为Map的key
open:整个循环内容开头的字符串。
close:整个循环内容结尾的字符串。
separator:每次循环的分隔符。

collection的属性设置方法:
以下代码是DefaultSqlSession 中的方法,也是默认情况下的处理逻辑:
private Object wrapCollection(final Object object) {
  if (object instanceof Collection) {
    StrictMap<Object> map = new StrictMap<Object>();
    map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
        return map;
      }
  } else if (object != null && object.getClass().isArray()) {
    StrictMap<Object> map = new StrictMap<Object>();
    map.put("array", object);
    return map;
  }
  return object;
}
当参数类型为集合的时候,默认会转换为Map 类型,并添加一个key 为"collection",如果参数类型是List 集合,那么就继续添加一个key 为"list",这样,当collection="list"时,就能得到这个集合,并对它进行循环操作。
当参数类型为数组的时候,也会转换成Map类型,默认的key为"array",使用数组参数时,就需要把foreach标签中的collection属性值设置为array。

List --> list 或者 collection
set --> collection
数组 --> array
map --> _parameter
推荐使用@Param 来指定参数的名字,这时collection 就设置为通过@Param 注解指定的名字。

SQL语句中有时会使用IN关键字,例如idin(1,2,3)。可以使用${ids}方式直接获取值,但这种写法不能防止SQL注入,想避免SQL注入就需要用#{}的方式,这时就要配合使用foreach标签来满足需求。
foreach 实现in 集合或数组
List<SysUser> selectByIdList(List<Long> idList);
<select id= "selectByIdList" resultType= "test.mybatis.simple.model.SysUser">
select id,user_name,user_password,user_email,user_Info,head_img,create_time
from sys_user
where id in
<foreach collection= "list" open="(" close=")" separator= "," item= "id" index = "index">
  #{id}
</foreach>
</select>

foreach 实现批量插入
如果数据库支持批量插入,就可以通过foreach 来实现。批量插入是SQL-92 新增的特性,目前支持的数据库有DB2, SQL Server 2008 及以上版本、PostgreSQL8.2及以上版本、MySQL、SQLite 3.7.11及以上版本、H2 。批量插入的语法如下。
INSERT INTO tablename (column-a, [column-b, ... ])
VALUES ('value-la', ['value-lb', ...]),
('value-2a', ['value-2b', ...]),
...
int insertList(List<SysUser> userList);
<insert >

foreach 实现动态UPDATE
int updateByMap(Map<String, Object> map);
<update >
  ${key} = #{val}
</foreach>
where id = #{id}
</update>