一、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 * 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 循环中把实际值赋给其中每个项。