mybatis foreach标签(超详细)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
什么是MyBatis的foreach标签?
MyBatis的foreach
标签是动态SQL中一个非常强大的工具,它允许我们在SQL语句中循环遍历集合数据(如List、数组、Map等),从而实现灵活的条件查询或批量操作。简单来说,它就像是一个“批量处理的瑞士军刀”,能够将原本需要多次执行的SQL操作合并为一次执行,显著提升效率。
想象一下,快递分拣中心的传送带:每个包裹(数据元素)被逐一扫描并分拣到对应的区域(SQL条件)。foreach
标签的作用,就是让MyBatis自动完成这个分拣过程,开发者只需要定义规则即可。
基础语法与核心概念
1. 基本语法结构
foreach
标签的XML语法如下:
<foreach
collection="集合名称"
item="元素别名"
index="索引变量(可选)"
open="循环前添加的字符串"
separator="元素之间的分隔符"
close="循环后添加的字符串"
>
<!-- 被循环的SQL片段 -->
</foreach>
- collection:指定遍历的集合类型,如
list
、array
、map.entrySet
等。 - item:定义每次循环中当前元素的变量名。
- index:当遍历Map时,可用此变量表示键值对的索引(默认为键)。
- open/separator/close:分别定义循环前、元素间、循环后的字符串,类似模板拼接。
2. 参数传递与集合类型
在使用foreach
时,需要将集合数据通过MyBatis的参数传递到SQL语句中。例如:
// Java代码传递参数
List<String> ids = Arrays.asList("1", "2", "3");
mapper.selectByIds(ids);
// XML中接收参数
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
这里,collection="list"
表示接收的是一个List
类型参数,每个元素被赋值给item="id"
。最终生成的SQL为:
SELECT * FROM user WHERE id IN (1, 2, 3)
常见用法场景与案例
1. IN条件的批量查询
这是foreach
最经典的用法,适用于多个ID或值的快速筛选。
案例需求:根据用户输入的多个用户ID,查询对应的所有用户信息。
<!-- XML配置 -->
<select id="selectUsersByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
// 调用示例
List<Integer> ids = Arrays.asList(101, 102, 103);
List<User> users = userMapper.selectUsersByIds(ids);
2. BETWEEN条件的动态生成
当需要根据多个区间进行查询时,foreach
可以灵活生成多个BETWEEN
条件。
案例需求:查询价格在多个区间内的商品,例如价格在[100, 200]或[500, 600]之间的商品。
<select id="selectProductsByPriceRanges" resultType="Product">
SELECT * FROM product
WHERE (
<foreach collection="list" item="range" separator=" OR ">
price BETWEEN #{range.start} AND #{range.end}
</foreach>
)
</select>
// Java参数构造
List<PriceRange> ranges = Arrays.asList(
new PriceRange(100, 200),
new PriceRange(500, 600)
);
List<Product> products = productMapper.selectProductsByPriceRanges(ranges);
此时,生成的SQL会自动组合多个BETWEEN
条件,并用OR
连接。
3. 批量插入与更新
foreach
还能简化批量操作的SQL编写,避免手动拼接重复代码。
案例需求:批量插入多条用户数据。
<insert id="insertUsers">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
// 调用示例
List<User> users = Arrays.asList(
new User("Alice", 30),
new User("Bob", 25)
);
userMapper.insertUsers(users);
4. 自定义分隔符与模板
通过open
、separator
、close
属性,可以精确控制生成的SQL格式。例如:
<!-- 生成逗号分隔的字符串列表 -->
<foreach item="tag" collection="tags" open="(" separator="," close=")">
#{tag}
</foreach>
输出结果类似:(Java, Python, SQL)
。
进阶技巧与高级用法
1. 遍历Map的键值对
当需要遍历Map时,可以通过index
和item
分别获取键和值:
<!-- 查询多个字段的组合条件 -->
<select id="selectByMap" resultType="User">
SELECT * FROM user
WHERE
<foreach collection="map.entrySet" item="entry" separator=" AND ">
${entry.key} = #{entry.value}
</foreach>
</select>
// 参数示例
Map<String, Object> params = new HashMap<>();
params.put("name", "Alice");
params.put("age", 30);
List<User> users = userMapper.selectByMap(params);
生成的SQL为:WHERE name = 'Alice' AND age = 30
。
2. 动态构建JOIN语句
在复杂查询中,foreach
可动态拼接多个JOIN条件。例如:
<!-- 根据表名列表动态JOIN多个关联表 -->
<select id="selectWithDynamicJoins">
SELECT * FROM main_table
<foreach collection="tables" item="table" open=" " separator=" " close="">
LEFT JOIN #{table} ON main_table.id = #{table}_id
</foreach>
</select>
但需注意:动态表名可能引发SQL注入风险,需严格校验输入。
3. 分页与性能优化
在使用foreach
处理大数据时,需结合分页技术避免单次查询过大。例如:
<!-- 分页查询 -->
<select id="selectUsersWithPagination" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
LIMIT #{offset}, #{limit}
</select>
或使用MyBatis的RowBounds
分页参数:
List<User> users = userMapper.selectUsersByIds(ids, new RowBounds(offset, limit));
性能优化与常见问题
1. 避免参数类型不匹配
若传递的参数类型与collection
属性不匹配(如将Map传入collection="list"
),会引发Unclosed cursor
等异常。始终确保:
- List →
collection="list"
- Map →
collection="map"
或collection="map.entrySet"
- 数组 →
collection="array"
2. 分页与IN子句的冲突
当IN条件中的元素过多时(如超过数据库的SQL长度限制),需拆分请求。例如:
// 拆分List为多个小批量
int batchSize = 1000;
for (int i = 0; i < ids.size(); i += batchSize) {
List<Integer> subList = ids.subList(i, Math.min(i + batchSize, ids.size()));
// 执行分批查询
}
3. 预编译参数的使用
避免直接拼接字符串(如${item}
),改用#{item}
防止SQL注入。例如:
<!-- 正确写法 -->
WHERE column = #{item}
<!-- 错误写法(可能引发注入风险) -->
WHERE column = ${item}
实战案例:复杂查询的动态构建
假设需要根据用户输入的多个条件(如年龄范围、地区、性别)动态生成查询:
1. 需求描述
用户可输入任意组合的筛选条件,包括:
- 年龄范围(如18-30岁)
- 地区列表(如["北京", "上海"])
- 性别(如男/女)
2. 实现步骤
<!-- Mapper XML -->
<select id="searchUsers" resultType="User">
SELECT * FROM user
WHERE 1=1
<if test="ageStart != null and ageEnd != null">
AND age BETWEEN #{ageStart} AND #{ageEnd}
</if>
<if test="regions != null and !regions.isEmpty()">
AND region IN
<foreach item="region" collection="regions" open="(" separator="," close=")">
#{region}
</foreach>
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
</select>
// 调用示例
SearchRequest req = new SearchRequest();
req.setAgeStart(18);
req.setAgeEnd(30);
req.setRegions(Arrays.asList("Beijing", "Shanghai"));
req.setGender("M");
List<User> users = userMapper.searchUsers(req);
3. 生成的SQL示例
SELECT * FROM user
WHERE 1=1
AND age BETWEEN 18 AND 30
AND region IN ('Beijing', 'Shanghai')
AND gender = 'M'
结论与扩展学习
通过本文,我们系统学习了MyBatis foreach
标签的核心功能、语法细节及实战案例。它不仅是处理批量操作的高效工具,更是构建动态SQL的“瑞士军刀”。
对于进阶开发者,建议进一步探索以下方向:
- 动态SQL的其他标签:如
<if>
、<choose>
、<trim>
等,结合foreach
实现更复杂的逻辑。 - MyBatis-Plus:基于MyBatis的增强框架,提供更简洁的批量操作API。
- 数据库性能调优:分析
foreach
生成的SQL执行计划,避免全表扫描。
掌握foreach
标签,不仅能提升代码的复用性和可维护性,更是构建灵活、高效数据访问层的关键一步。希望本文能成为你MyBatis学习路上的可靠指南!