Mybatis foreach collection详解

发布时间:2023-05-17

一、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,再处理 dbTablefieldNamesfieldValues

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