乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

foreach

导学

本节你将掌握 MyBatis 动态 SQL 中用于集合遍历的核心元素 foreach。学习目标是:

  • 理解 foreach 在动态 SQL 中的核心作用(将 Java 集合展开为 SQL 片段)
  • 掌握 collection、item、index、open、separator、close 六个核心属性
  • 理解 collection 的三种取值来源:list、array、Map.key 和 @Param 指定名
  • 能够编写基于 foreach 的 IN 条件查询、批量插入、批量更新

定义

foreach 是 MyBatis 动态 SQL 中用于遍历集合元素的核心元素。它将 Java 中的集合(List、Array、Map)迭代展开为 SQL 语句中的多个值或记录,广泛应用于以下场景:

  • IN 条件查询:WHERE id IN (1, 2, 3)
  • 批量插入:INSERT INTO student (name, major) VALUES ('大翔', '计算机科学'), ('白歌', '软件工程')
  • 批量更新:UPDATE student SET major = '人工智能' WHERE id IN (1, 2, 3)

在 JDBC 编程中,处理集合参数需要手动拼接占位符:

List<Integer> ids = Arrays.asList(1, 2, 3);
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String sql = "SELECT * FROM student WHERE id IN (" + placeholders + ")";
// 然后逐个设置参数...

这种写法不仅繁琐,而且拼接过程容易出错。MyBatis 的 foreach 元素将集合遍历逻辑声明式地表达在 XML 中,自动处理占位符生成和参数绑定,既简洁又安全。


适用位置与核心属性

foreach 可以嵌套在 select、insert、update、delete 语句内部,或嵌套在 where、set、trim 等容器元素内部。

属性是否必填说明
collection是指定要遍历的集合。取值可以是 list(List 类型参数)、array(数组参数)、Map 的 key,或 @Param 注解指定的名称
item是集合中当前元素的别名,在 foreach 内部通过该别名引用当前元素
index否当前元素的索引(List/Array 时为下标,Map 时为 key)。可选,默认不绑定索引
open否在遍历结果前添加的字符串,如 (
separator否每个元素之间的分隔符,如 ,
close否在遍历结果后添加的字符串,如 )

核心原理

foreach 集合遍历展开流程图


完整示例

场景说明

乐途学院的学生管理系统需要实现以下三个基于集合遍历的功能:

  1. 根据 ID 列表查询学生:传入一个 ID 列表,查询对应的学生记录
  2. 批量插入学生:传入一个学生列表,一次性插入多条记录
  3. 根据 ID 数组批量更新专业:传入一个 ID 数组,将对应学生的专业统一更新

要求分别展示 collection 为 list、array、Map 三种情况的示例。

操作前的数据库表结构及初始数据

CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    age INT,
    major VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);

当前表数据:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

完整的映射文件片段

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.flying.mapper.StudentMapper">

    <!-- 示例1:collection 为 list,IN 条件查询 -->
    <select id="findByIdList" resultType="Student">
        SELECT id, name, age, major, score
        FROM student
        WHERE id IN
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

    <!-- 示例2:collection 为 array,IN 条件查询 -->
    <select id="findByIdArray" resultType="Student">
        SELECT id, name, age, major, score
        FROM student
        WHERE id IN
        <foreach collection="array" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

    <!-- 示例3:collection 为 @Param 指定名,IN 条件查询 -->
    <select id="findByIdParam" resultType="Student">
        SELECT id, name, age, major, score
        FROM student
        WHERE id IN
        <foreach collection="idList" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

    <!-- 示例4:collection 为 Map 的 key,批量更新 -->
    <update id="updateMajorByIdMap" parameterType="java.util.HashMap">
        UPDATE student
        SET major = #{newMajor}
        WHERE id IN
        <foreach collection="idMap.keys" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </update>

    <!-- 示例5:批量插入学生(使用 foreach 遍历对象列表) -->
    <insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO student (name, age, major, score)
        VALUES
        <foreach collection="list" item="student" separator=",">
            (#{student.name}, #{student.age}, #{student.major}, #{student.score})
        </foreach>
    </insert>

    <!-- 示例6:使用 index 属性,遍历带索引的场景 -->
    <update id="batchUpdateScore" parameterType="java.util.HashMap">
        <foreach collection="scoreMap" item="score" index="stuId" separator=";">
            UPDATE student
            SET score = #{score}
            WHERE id = #{stuId}
        </foreach>
    </update>

</mapper>

StudentMapper.java(接口)

package com.flying.mapper;

import com.flying.entity.Student;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;

public interface StudentMapper {

    // collection = "list"
    List<Student> findByIdList(List<Integer> idList);

    // collection = "array"
    List<Student> findByIdArray(int[] idArray);

    // collection = "idList"(@Param 指定)
    List<Student> findByIdParam(@Param("idList") List<Integer> ids);

    // collection = "idMap.keys"(Map 的 key 集合)
    int updateMajorByIdMap(Map<String, Object> paramMap);

    // 批量插入
    int batchInsert(List<Student> students);

    // 使用 index 遍历 Map
    int batchUpdateScore(Map<Integer, Double> scoreMap);
}

实际执行结果

示例1:collection 为 list(IN 查询)

测试代码:

List<Integer> idList = Arrays.asList(1, 3, 5);
List<Student> list = mapper.findByIdList(idList);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE id IN (?, ?, ?)

参数值: 1, 3, 5

查询结果集:

idnameagemajorscore
1大翔22计算机科学95.50
3小崔20计算机科学92.00
5李眉22软件工程87.00

分析: collection="list" 表示参数是一个 List 对象。foreach 遍历该列表,每个元素用 #{id} 引用,open="(" 和 close=")" 生成括号,separator="," 在元素间添加逗号。

示例2:collection 为 array(IN 查询)

测试代码:

int[] idArray = {2, 4};
List<Student> list = mapper.findByIdArray(idArray);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE id IN (?, ?)

参数值: 2, 4

查询结果集:

idnameagemajorscore
2白歌21软件工程88.00
4黄俪21信息安全90.50

分析: collection="array" 表示参数是一个数组对象。foreach 对数组的遍历与 List 完全一致,只是 collection 的取值不同。

示例3:collection 为 @Param 指定名(IN 查询)

测试代码:

List<Integer> ids = Arrays.asList(1, 2, 3, 4);
List<Student> list = mapper.findByIdParam(ids);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE id IN (?, ?, ?, ?)

参数值: 1, 2, 3, 4

查询结果集:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50

分析: 当方法参数有多个时,或者为了代码可读性,可以使用 @Param("idList") 注解为参数命名。此时 collection 的值就是注解中指定的名称 "idList",而不是默认的 "list"。

示例4:collection 为 Map 的 key 集合(批量更新)

测试代码:

Map<Integer, String> idMap = new HashMap<>();
idMap.put(1, "dummy");  // value 无所谓,只用 key
idMap.put(2, "dummy");

Map<String, Object> paramMap = new HashMap<>();
paramMap.put("idMap", idMap);
paramMap.put("newMajor", "人工智能");

mapper.updateMajorByIdMap(paramMap);

最终生成的 SQL 语句:

UPDATE student SET major = ? WHERE id IN (?, ?)

参数值: 人工智能, 1, 2

执行后表数据:

idnameagemajorscore
1大翔22人工智能95.50
2白歌21人工智能88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

分析: collection="idMap.keys" 表示遍历 Map 的 key 集合。在 foreach 内部,#{id} 引用的是 Map 的每一个 key。这是 foreach 处理 Map 的典型方式。

示例5:批量插入(遍历对象列表)

测试代码:

List<Student> students = new ArrayList<>();
Student s1 = new Student();
s1.setName("乐途");
s1.setAge(23);
s1.setMajor("数据科学");
s1.setScore(93.0);

Student s2 = new Student();
s2.setName("蓝天");
s2.setAge(22);
s2.setMajor("网络安全");
s2.setScore(89.5);

students.add(s1);
students.add(s2);

mapper.batchInsert(students);

最终生成的 SQL 语句:

INSERT INTO student (name, age, major, score) VALUES (?, ?, ?, ?), (?, ?, ?, ?)

参数值: 乐途, 23, 数据科学, 93.0, 蓝天, 22, 网络安全, 89.5

执行后表数据(新增两条):

idnameagemajorscore
...............
6乐途23数据科学93.00
7蓝天22网络安全89.50

分析: item="student" 表示当前元素是一个 Student 对象,在 foreach 内部通过 #{student.name}、#{student.age} 等方式引用对象的属性。separator="," 在每个 VALUES 元组之间添加逗号,实现一条 SQL 插入多条记录。

示例6:使用 index 遍历 Map(批量更新分数)

测试代码:

Map<Integer, Double> scoreMap = new HashMap<>();
scoreMap.put(3, 95.0);   // id=3 的学生分数改为 95.0
scoreMap.put(5, 91.5);   // id=5 的学生分数改为 91.5

mapper.batchUpdateScore(scoreMap);

最终生成的 SQL 语句(MySQL 多语句模式):

UPDATE student SET score = ? WHERE id = ?;
UPDATE student SET score = ? WHERE id = ?

参数值: 95.0, 3, 91.5, 5

执行后表数据:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学95.00
4黄俪21信息安全90.50
5李眉22软件工程91.50

分析: collection="scoreMap" 遍历 Map 时,item="score" 是 Map 的 value,index="stuId" 是 Map 的 key。separator=";" 在每个 UPDATE 语句之间添加分号。注意:这种多语句执行需要在 JDBC URL 中开启 allowMultiQueries=true。


易错场景 / 常见误区

误区错误示例后果正解
集合为空时不做校验传入空 Listforeach 不输出内容,SQL 变成 WHERE id IN (),语法错误Java 层前置校验集合非空,或在 XML 中用 if 包裹 foreach
collection 值写错collection="ids" 但参数未用 @Param("ids")报错 "There is no getter for property named 'ids'"单参数 List 用 collection="list",单参数 Array 用 collection="array",多参数用 @Param 指定名
批量插入时忘记 separatorseparator 为空多个 VALUES 元组之间没有逗号,SQL 语法错误separator=","
在 foreach 内部用错 item 别名#{name} 但 item 是 student报错找不到属性 name使用 #{student.name} 或确保 item 别名与引用一致
批量更新用分号但 JDBC 未开启多语句separator=";"驱动拒绝执行多条语句在 JDBC URL 添加 allowMultiQueries=true,或改用单条 UPDATE + CASE WHEN
混淆 item 和 index以为 index 是元素值index 是索引/key,item 才是元素值需要元素值时用 item,需要索引/key 时用 index

面试考点

Q1:foreach 的 collection 属性有哪些常见取值?

A:collection 的常见取值包括:① "list":当传入参数是单个 List 时;② "array":当传入参数是单个数组时;③ "Map的key" 或 "map.keys":当遍历 Map 的 key 集合时;④ @Param 注解指定的名称:当方法参数有多个,或为了可读性显式命名时。如果参数是单个对象且对象内部有集合属性,则 collection 可以写该属性的路径,如 "student.hobbies"。

Q2:以下代码有什么问题?

<foreach collection="ids" item="id" open="(" close=")">
    #{id}
</foreach>

A:缺少 separator=","。当集合有多个元素时,元素之间没有分隔符,生成的 SQL 会变成 (1 2 3),而不是 (1, 2, 3),导致语法错误。正确写法是添加 separator=","。

Q3:foreach 能否用于批量插入?有什么注意事项?

A:可以。批量插入时,foreach 遍历对象列表,item 是当前对象,separator="," 在每个 VALUES 元组之间添加逗号。注意事项:① 确保数据库和驱动支持批量插入语法;② 大量数据时建议分批处理(如每批 500~1000 条),避免 SQL 过长导致内存或网络问题;③ 使用 useGeneratedKeys 获取自增 ID 时,部分数据库驱动对批量插入的主键返回支持有限。

Q4:foreach 遍历 Map 时,item 和 index 分别代表什么?

A:遍历 Map 时,index 代表当前 Map 条目的 key,item 代表当前 Map 条目的 value。例如 <foreach collection="scoreMap" item="score" index="stuId"> 中,stuId 是学生的 ID(key),score 是分数值(value)。


小结

foreach 是 MyBatis 动态 SQL 中处理集合遍历的核心元素,它将 Java 中的 List、Array、Map 展开为 SQL 中的多个值或记录。通过 collection、item、index、open、separator、close 六个属性,foreach 可以灵活应对 IN 查询、批量插入、批量更新等多种场景。

使用 foreach 时需要注意:① 确保集合非空,或在 XML 中用 if 做前置判断;② 单参数 List 用 collection="list",单参数 Array 用 collection="array";③ 多参数或需要可读性时用 @Param 指定名称;④ 批量操作注意数据库驱动限制和 SQL 长度限制。

下一节我们将学习 bind 元素,它是 OGNL 上下文变量绑定工具,常用于模糊查询中拼接 % 通配符。


下一章引子

当你需要在 XML 映射文件中创建一个临时变量(如在模糊查询前拼接 % 通配符),或者需要复用一个复杂的 OGNL 表达式时,bind 元素是你的得力助手。它能在 OGNL 上下文中创建一个新变量,供后续的 SQL 片段引用。继续阅读,掌握动态 SQL 中的变量绑定技巧。

上一页
set
下一页
trim