MyBatis的动态SQL
基本概念:
在MyBatis中,在配置XML映射时,可以使用某些逻辑节点,实现逻辑判断或者循环等等,使得每次执行的SQL语句是可能随着参数发生变化的,即:参数不同,最终执行的SQL语句可能不同,所以,称之为“动态SQL”。
<foreach>标签:
例如存在以下需求:根据多个id同时删除多条数据。则抽象方法可以设计为:
Integer deleteByIds(Integer[] ids);
需要执行的SQL语句例如:
DELETE FROM t_user WHERE id IN (?,?,?);
但是,以上SQL语句中的问号所表示的值的数量是不确定的,将根据调用方法时的参数`Integer ids`来决定,所以,可以使用`<foreach>`来实现:
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候
<delete id="deleteByIds"> DELETE FROM t_user WHERE id IN ( <foreach collection="array" item="id" separator=","> #{id} </foreach> ) </delete>
在`<foreach>`的配置中,常用属性有:
- `collection`:如果映射的抽象方法只有1个参数时,如果遍历的数据类型是数组,则取值为`array`,如果遍历的数据类型是List集合,则取值为`list`;如果映射的抽象方法有多个参数,则取值为注解中的名称;
如下面的抽象方法就有两个参数:
Integer deleteByIds(
@Param("ids") Integer[] ids,
@Param("str") string str)
那么<foreach>标签中的collection属性值就是ids
<delete id="deleteByIds"> DELETE FROM t_user WHERE id IN <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
- `item`:遍历过程中元素的名称,相当于Java代码中`for (Integer id : ids)`中的`id`,在动态SQL中,也是`<foreach>`节点的子级中通过`#{}`表示的变量名
- `separator`:分隔符,例如在`IN`语句中应该是`id IN (6,8,9)`,各个值之间需要使用逗号进行分隔,所以,以上代码中取值为`,`
- `open`:整个遍历得到的SQL语句的部分的最左侧的字符,例如在SQL语句中不写括号时,该属性的值可以是`(`
- `close`:与`open`对应,是SQL语句的部分的最右侧的字符,例如`)`
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
<delete id="deleteByIds"> DELETE FROM t_user WHERE id IN <foreach collection="array" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
测试代码:
@Test public void deleteByIds() { Integer[] ids = { 2,1,5,8,9,10,12,14,4 }; Integer rows = mapper.deleteByIds(ids); System.out.println("rows=" + rows); }
<if>标签:
假设希望提供某个查询功能,尽量满足所有的查询需求,即:可以通过参数决定WHERE子句,决定ORDER BY子句,甚至决定LIMIT子句,则抽象方法可以设计为:
List<User> find( @Param("where") String where, @Param("orderBy") String orderBy, @Param("offset") Integer offset, @Param("count") Integer count);
匹配的映射为:
<select id="find" resultType="cn.tedu.mybatis.entity.User"> SELECT id, age, password, username, phone, email FROM t_user <if test="where != null"> WHERE ${where} </if> <if test="orderBy != null"> ORDER BY ${orderBy} </if> <if test="offset != null"> LIMIT #{offset},#{count} </if> </select>
可以看到,`<if>`用于进行逻辑判断,在标签内的`test`中,如果需要表示参数,是不需要添加`#{}`格式的,直接写参数名称即可(即使只有1个参数,也应该正确的填写参数名称)。
在以上映射的SQL中,有2种占位符,分别使用`#{}`和`${}`,前者,用于对值进行占位,可以替换在预编译的SQL语句中的`?`,在实际执行时,也是预编译的,所以,在使用`#{}`格式时,无须考虑参数的类型,后者,用于对非值的语句部分进行占位,在实际执行时,是直接拼接到SQL语句中的,并不具备预编译的效果。例如以上`String where`参数表示的是SQL语句中的WHERE子句,如果根据id查询数据,其值可以是`String where ="id=1";`,如果根据用户名查询数据,则值应该是`String where = "username='Jack'";`。
> 通常,并不建议使用某1个方法及其对应的映射来完成许多不同的操作,例如以上功能几乎可以完成所有的单表简单查询,但是,不应该这样使用,因为,为了实现更加通用的效果,可能需要很多`<if>`进行判断,执行时效率偏低,并且,不同的功能,对于查询的字段要求是不一样的,也许某个功能只需要查询20个字段中的2个而已,而通用的查询如果把20个字段全部查出来,内存开销也会有浪费!
其它动态语句的用法可以看:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html