您的位置:

Mybatis foreach collection详解

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