MyBatis foreach

1. 前言

在 MyBatis 中,常常会遇到集合类型的参数,虽然我们可以通过 OGNL 表达式来访问集合的某一个元素,但是 OGNL 表达式无法遍历集合。foreach 标签就是专门用来解决这类问题的,本小节我们就来一起学习它。

2. 定义

慕课解释:foreach 标签用来遍历数组、列表和 Map 等集合参数,常与 in 关键字搭配使用。

3. 实例

我们以 3 个例子来看一看 foreach 是如何遍历列表、数组和 Map 的。

3.1 遍历列表

xml:

<select id="selectUserInIds" resultType="com.imooc.mybatis.model.User">
  SELECT * FROM imooc_user
  WHERE id IN
  <foreach collection="list" open="(" close=")" separator="," item="item" index="index">
    #{item}
  </foreach>
</select>

Java:

List<User> selectUserInIds(List<Integer> ids);

上面是 selectUserInIds 方法在 java 和 xml 中对应的代码段。

foreach 标签共有 6 个属性,它们的作用分别为:

  • collection: 被遍历集合参数的名称,如 list;
  • open: 遍历开始时插入到 SQL 中的字符串,如 ( ;
  • close: 遍历结束时插入到 SQL 中的字符串,如 ) ;
  • separator: 分割符,在每个元素的后面都会插入分割符;
  • item: 元素值,遍历集合时元素的值;
  • index: 元素序列,遍历集合时元素的序列。

当 selectUserInIds 方法的参数 ids 为Arrays.asList(1, 2)时,生成的 SQL 语句为:

SELECT * FROM imooc_user WHERE id IN ( 1 , 2 ) 

foreach 标签的 collection 属性在接受参数名有两种情况:一、匿名参数,当在 java 方法中没有通过 @Param 注解指定参数名时,列表类型的使用默认参数名 list。二、具名参数,java 方法中使用了@Param 注解指定了参数名称,则 foreach 中的 collection 属性必须为参数名,如:

List<User> selectUserInIds(@Param("ids") List<Integer> ids);
<foreach collection="ids" open="(" close=")" separator="," item="item" index="index">
  #{item}
</foreach>

我们推荐你为列表类型参数用注解指定一个名称,让使用该名称来遍历,方便代码维护和阅读。

3.2 遍历数组

当 Java 方法使用的参数类型为数组时,如下:

List<User> selectUserInIds(Integer[] ids);

如果 ids 参数使用 @Param 注解指定了参数名称,则 foreach 标签中的 collection 属性必须为该名称;但若未指定名称,则在 foreach 标签中使用默认数组名称 array,如下:

<select id="selectUserInIds" resultType="com.imooc.mybatis.model.User">
  SELECT * FROM imooc_user
  WHERE id IN
  <foreach collection="array" open="(" close=")" separator="," item="item" index="index">
    #{item}
  </foreach>
</select>

3.3 遍历 Map

当 Java 方法使用的参数类型为 Map 时,如下:

int updateUserById(@Param("params") Map map, @Param("id") Integer id);

使用 foreach 标签遍历 Map 时,collection 属性值为注解指定的参数名,即 params,且 item 是 Map 的键值,index 是键名。

<update id="updateUserById">
  UPDATE imooc_user
  SET
  <foreach collection="params" item="val" index="key" separator=",">
    ${key} = #{val}
  </foreach>
  WHERE id = #{id}
</update>

注意: 由于 key 是字段名称,因此不能使用#{}作为占位符,只能使用${}在字符串中替换。

updateUserById 生成的 SQL 语句大致如下:

UPDATE imooc_user SET score = ? , age = ? WHERE id = ? 

4. 实践

4.1 例1. 使用名称批量查询用户

请使用 MyBatis 完成对 imooc_user 表使用名称批量查询用户的功能,参数为一个名称列表,使用 in 关键字进行查询。

分析:

按照 MyBatis 的开发模式,先在 UserMapper.xml 文件中添加使用名称批量查询用户的 select 标签,然后在 UserMapper.java 中添加上对应的方法。

步骤:

首先,在 UserMapper.xml 中添加 select 标签,并在标签中写入 SQL,使用 foreach 标签来遍历名称列表。

<select id="selectUserInNames" resultType="com.imooc.mybatis.model.User">
  SELECT * FROM imooc_user
  WHERE username IN
  <foreach collection="names" open="(" close=")" separator="," item="item" index="index">
    #{item}
  </foreach>
</select>

然后在 UserMapper.java 中添加上对应的接口方法:

package com.imooc.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.imooc.mybatis.model.User;

@Mapper
public interface UserMapper {
   List<User> selectUserInNames(@Param("names") List<String> names);
}

结果:

通过如下代码,我们运行 selectUserInNames 这个方法。

UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> users = userMapper.selectUserInNames(Arrays.asList("pedro", "peter"));
System.out.println(users);

成功后,结果为:

[User{id=1, username='peter', age=18, score=100}, User{id=2, username='pedro', age=24, score=200}]

4.2 例2. 批量插入用户

请使用 MyBatis 完成对 imooc_user 表批量插入用户的功能,参数为一个用户列表。

分析:

同上。

步骤:

首先,在 UserMapper.xml 中添加 insert 标签,并在标签中写入 SQL,使用 foreach 标签来遍历用户列表。

<insert id="insertUsers">
  INSERT INTO imooc_user(username,age,score)
  VALUES
  <foreach collection="users" item="user" separator=",">
    (#{user.username}, #{user.age}, #{user.score})
  </foreach>
</insert>

注意,这里遍历 users 得到的单位是 user,user 是一个对象,因此必须通过 OGNL 表达式来取 user 的属性。

然后在 UserMapper.java 中添加上对应的接口方法:

package com.imooc.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.imooc.mybatis.model.User;

@Mapper
public interface UserMapper {
   int insertUsers(@Param("users") List<User> users);
}

结果:

通过如下代码,我们运行 insertUsers 这个方法。

User user1 = new User();
user1.setUsername("user1");
user1.setScore(100);
user1.setAge(0);
User user2 = new User();
user2.setUsername("user2");
user2.setScore(210);
user2.setAge(20);
int rows = userMapper.insertUsers(Arrays.asList(user1, user2));
System.out.println(rows);

成功后,结果为:

2

5. 小结

  • foreach 标签是使用非常广泛的一个标签,当使用 SQL 进行批量插入、查询时都需要使用到它。
  • 列表遍历的使用最为广泛,数组和 Map 则相对较少。