一、forEach collection的基本使用
在Mybatis中,forEach
可以用来循环遍历一个集合(collection)或数组(array)的元素,用于动态生成SQL语句。
最常用的 forEach
写法如下所示:
<select id="queryByList" resultType="com.example.demo.model.User">
select * from user
where id in
<foreach collection="list" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</select>
解释:
在这个例子中,list
是从Java代码中传递过来的 List<Integer>
集合,其里面包含了这个查询需要查的ID(不能是Long,否则如果条件里面是id,则会报类型不匹配),然后在 SQL语句中,将list的内容动态生成一个查询条件。
如果 list
里面只有一个元素,那么该SQL会被渲染成:
select * from user where id in (1)
如果 list
里面有两个及以上的元素,那么该SQL会被渲染成:
select * from user where id in (1, 2)
二、collection里面是Map的处理
当我们有一个 Map<String, Integer>
作为输入参数的时候,forEach
的写法如下:
<select id="queryByMap" resultType="com.example.demo.model.User">
select * from user
where id in
<foreach collection="map" index="key" item="value" open="(" close=")" separator=",">
#{value}
</foreach>
</select>
解释:
map
是从Java代码中传递过来的 Map<String, Integer>
集合,其里面包含了这个查询需要查的ID(不能是Long,否则如果条件里面是id,则会报类型不匹配),其中 key
是map的键,value
是map的值,在SQL语句中,将Map的值动态生成一个查询条件。比如这样实现,则只会查询出ID为 1 和 3的这两条数据。
List<User> list = userDao.queryByMap(new HashMap<String, Integer>() {{
put("A", 1);
put("B", 2);
put("C", 3);
}});
三、基于collection的动态SQL
假设我们有这样的一种需求:当 map
这个参数中的 key
值为这五个关键字的时候,就需要暂停一下这个SQL的执行,同时输出一段 SQL 提示语句。
要实现这个目标,可以像下面这样:
<select id="queryByMap" resultType="com.example.demo.model.User">
select * from user
where id in
<foreach collection="map" index="key" item="value" open="(" close=")" separator=",">
<if test="'A,B,C,D,E'.indexOf(key)>=0">
<bind name="sql" value="'select \'These are not allowed!\'; '"/>
</if>
#{value}
</foreach>
${sql}
</select>
解释: 这样写会把 SQL 脚本渲染成:
select * from user where id in (
select 'These are not allowed!';
1, 2, 3, 4, 5
)
注意:这里的 sql
是一个绑定,他会被生效,然后其结果也会再次渲染在结果中。
四、动态SQL语句的顺序结构
假设有这样一个查询场景:能够动态选择一个或多个数据库名字,查询名字以某个子串开头的玩家列表。当然,数据库表和字段名、字段值也都可以是参数传递进去。 假如是这样的代码:
<select id="queryByName" resultType="com.example.demo.model.User">
<select id="queryByName" resultType="com.example.demo.model.User">
select * from user
<where>
<if test="dbNames != null">
<foreach collection="dbNames" index="index" item="dbName">
<if test="index == 0">
(dbName=#{dbName}
</if>
<if test="index > 0">
or dbName=#{dbName}
</if>
<if test="index == dbNames.size()-1">
)
</if>
</foreach>
and
</if>
userName LIKE #{name}%
</where>
</select>
解释:
上述代码中,语句的先后顺序是先处理 dbName
,再处理 userName
,再处理 dbTable
、fieldNames
和 fieldValues
。
五、foreach和bind的扩展
一个数据库表的往往会有很多个字段,如果写在SQL中会显得有些冗长,所以可以用 <bind>
标签来进行简化。
下面是一个具体的例子。 假设有一个 User
表,需要可以动态地修改这个表的若干个字段名字值,那么这种参数传递的方式需要使用“标记与携带参数(tag and baggage)”方式,即将每个字段名用一个字串标记,并且把每个需要设置的字段的值都放到集合里面:
<update id="update" parameterType="java.util.Map">
update user
<set>
<foreach collection="fieldNames" index="index" item="field">
<if test="fieldValues[index] != null">
<bind name="x" value="'#{field}' = #{fieldValues[index]}'"/>
${x},${sql}
</if>
</foreach>
updated_at=now()
</set>
where id=#{id}
</update>
解释:
上述代码中,将 注入的SQL 语句的名字放到了一个列表里面,然后再在 foreach
循环中把实际值赋给其中每个项。