4.3 foreach集合展开与批量手术


文档摘要

4.3 foreach:集合展开与批量手术 本节摘要:foreach 把集合拆开缝进 SQL:展开成 IN 括号里的值序列、拼成多值 INSERT、或构造 CASE WHEN 批量更新。本节讲透六个属性、空集合防护与分批上限,收在"foreach 不是批量写入唯一解"的判断上。 动态拼装的最后一节,处理"一个参数是一堆值"的场景。 六个属性一次讲清 属性 | 作用 | 备注 collection | 要遍历的集合在参数里的名字 | 单参数 List 默认名 list;数组是 array;

4.3 foreach:集合展开与批量手术

本节摘要:foreach 把集合拆开缝进 SQL:展开成 IN 括号里的值序列、拼成多值 INSERT、或构造 CASE WHEN 批量更新。本节讲透六个属性、空集合防护与分批上限,收在"foreach 不是批量写入唯一解"的判断上。

动态拼装的最后一节,处理"一个参数是一堆值"的场景。

六个属性一次讲清

<select id="findByIds" resultType="User"> SELECT id, username, email FROM users WHERE id IN <foreach collection="ids" item="id" index="i" open="(" separator="," close=")"> #{id} </foreach> </select>
List<User> list = mapper.findByIds(Arrays.asList(1, 2, 3)); // ==> Preparing: SELECT ... WHERE id IN ( ? , ? , ? ) // ==> Parameters: 1(Integer), 2(Integer), 3(Integer)
属性 作用 备注
collection 要遍历的集合在参数里的名字 单参数 List 默认名 list;数组是 array;建议显式 Param 命名
item 每次迭代的元素变量名 片段内用井号引用
index 索引变量名 List 是下标,Map 是键
open / close 整个循环体前后的包裹符 IN 场景的括号
separator 元素之间的分隔符 IN 用逗号,多值 INSERT 也用逗号

collection 的取名规则是新手高频卡点:方法只传一个 List 且不加注解时,默认名是 list(数组是 array);参数复杂或想语义化,就用注解命名:

// 注解命名后,collection 与注解值对齐 List<User> findByIds(@Param("ids") List<Integer> ids);

用法一:IN 查询与空集合防护

背景:购物车页展示勾选的商品,后端拿到商品 id 列表查详情。

事故版本——用户购物车为空时,列表是空的:

mapper.findByIds(Collections.emptyList());

foreach 循环体零次执行,open 与 close 仍会输出,SQL 变成 WHERE id IN ( )——直接语法报错。防护在两个层面:

<!-- 防护一:XML 侧判断列表非空才拼 IN 片段 --> <select id="findByIds" resultType="User"> SELECT id, username, email FROM users <where> <if test="ids != null and ids.size() > 0"> id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </if> </where> </select>
// 防护二:服务层短路,空列表根本不发 SQL public List<User> cartItems(List<Integer> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } return mapper.findByIds(ids); }

双层防护不是重复——XML 层防的是"其他调用方忘判空",服务层短路省的是一次注定失败的数据库往返。

用法二:多值 INSERT

<insert id="insertBatch"> INSERT INTO users (username, email) VALUES <foreach collection="list" item="u" separator=","> (#{u.username}, #{u.email}) </foreach> </insert>
List<User> batch = List.of( new User("u1", "u1@example.com"), new User("u2", "u2@example.com")); mapper.insertBatch(batch); session.commit(); // ==> Preparing: INSERT INTO users (username, email) // VALUES ( ? , ? ) , ( ? , ? )

一批数据一条 SQL、一次往返,这是它快的原因;也是它危险的根源——行数乘列数就是占位符个数,行数过大时会撞数据库的参数上限或语句长度上限。2.3 节给出的纪律在此完整落地:几百行内放心用,接近千行就分批,字段宽的表把分批阈值再往低调。

用法三:CASE WHEN 批量更新

批量场景不止插入。一批订单各自改各自的状态,逐条 UPDATE 是 N 次往返,foreach 加 CASE WHEN 折成一条:

<update id="updateStatusBatch"> UPDATE orders SET status = <foreach collection="list" item="o" open="CASE id" separator=" "> WHEN #{o.id} THEN #{o.status} </foreach> ELSE status END WHERE id IN <foreach collection="list" item="o" open="(" separator="," close=")"> #{o.id} </foreach> </update>
// 输入:[{id=101, status=PAID}, {id=102, status=CLOSED}] // 生成:UPDATE orders SET status = // CASE id WHEN ? THEN ? WHEN ? THEN ? ELSE status END // WHERE id IN ( ? , ? )

一个 foreach 展开成 CASE 分支,另一个展开成 IN 条件,两个循环遍历同一个集合——open 与 separator 的组合让"CASE id"与"ELSE status END"精确包裹住循环体。这种写法可读性有门槛,超过几十行数据或分支逻辑复杂时,BATCH 执行器(2.3 节)是更朴素的选择。

04-03-fig01-3

index 的 Map 用法

遍历 Map 时 index 是键,item 是值——适合"列名到条件值"这类成对参数:

<!-- 遍历 Map:index 是键 item 是值(示意:批量校验键值对) --> <select id="findByPairs" resultType="User"> SELECT id, username FROM users <where> <foreach collection="pairs" index="col" item="val" separator=" AND "> ${col} = #{val} </foreach> </where> </select>

注意这里 ${col} 拼的是列名(结构位置,美元合法),#{val} 走值通道——2.2 节的分界线在 foreach 内同样生效,且列名入参必须先过白名单。

⚠️ 常见坑:把 foreach 当万能批量方案。它生成的是"一条更大的 SQL",超大 SQL 的解析、执行计划与网络传输都是新成本;另一头,嵌套 foreach(循环里再循环)生成的语句行数是笛卡尔积式的,上线前先用测试数据量演算一遍。

💡 关键直觉:foreach 的本质是"把集合结构翻译成 SQL 结构"——列表译成 IN 序列、行集合译成 VALUES 组、更新集译成 CASE 分支。翻译有损,规模一大就失真;此时换 BATCH 执行器,让 JDBC 层的批处理接管。

本节要点回顾

  • 六属性:collection 找集合(默认名 list 或 array)、item 取元素、index 取下标或键、open 与 close 包裹、separator 分隔
  • IN 空集合必炸:open 与 close 不依赖循环就会输出,双层防护(XML 判空加服务层短路)
  • 多值 INSERT 上限:占位符数等于行数乘列数,千行为界主动分批
  • CASE WHEN 批量更新:两个 foreach 协作,分支复杂时退回 BATCH 执行器
  • 美元与井号在循环内同样分界:结构位置过白名单,值位置一律井号

第 4 章收官。到这里,手术三段论的中段全部打通。下一章进入术后监护:缓存、类型缝合线、插件、代码生成与 Spring 集成。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U