在Java开发中,我们经常需要处理批量数据库操作。传统的一条条记录处理方式不仅效率低下,而且代码冗长。MyBatis的<foreach>标签为解决这一问题提供了优雅而强大的方案,能够极大简化批量操作和动态SQL的编写。
一、foreach标签基础:核心属性全解析
MyBatis的<foreach>标签允许在SQL语句中动态循环遍历集合或数组,将其元素应用到SQL中,生成灵活的查询和更新语句。
foreach标签的六个核心属性:
collection:要遍历的集合或数组,这是必须指定的属性,在不同情况下值会有所不同item:集合中每个元素在迭代时的别名index:在List和数组中代表元素序号,在Map中代表元素的keyopen:循环开始前添加的字符串,如”(“separator:每次迭代之间的分隔符,如”,”close:循环结束后添加的字符串,如”)”
<select id="selectByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
二、collection属性的奥秘:三种常见场景详解
collection属性的取值是foreach使用中最容易出错的地方,主要分为三种情况:
1. 传入单个List参数
当Mapper接口方法只接收一个List参数时,collection属性值应为“list”。
// Mapper接口方法
List<User> selectByIds(List<Integer> ids);
<!-- XML配置 -->
<select id="selectByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
2. 传入单个数组参数
当参数为数组时,collection属性值应为“array”。
// Mapper接口方法
List<User> selectByIds(int[] ids);
<!-- XML配置 -->
<select id="selectByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
3. 使用@Param注解显式命名(推荐)
这是最佳实践,可以避免混淆,提高代码可读性。
// Mapper接口方法
List<User> selectByIds(@Param("idList") List<Integer> ids);
<!-- XML配置 -->
<select id="selectByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
三、foreach实战:四大应用场景详解
1. IN查询 – 动态条件查询的利器
IN查询是foreach最常见的应用场景,特别适合根据多个ID查询记录的需求。
<select id="selectUsersByIds" resultType="User">
SELECT * FROM users
WHERE status = 1
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
大数据量IN查询的优化:当ID数量很大时(如超过1000个),直接使用IN查询会导致性能问题。解决方案是分批查询:
public List<User> selectByIds(List<Long> ids) {
List<User> result = new ArrayList<>();
int batchSize = 1000; // 每批1000个
for (int i = 0; i < ids.size(); i += batchSize) {
List<Long> subList = ids.subList(i, Math.min(i + batchSize, ids.size()));
result.addAll(userMapper.selectByIds(subList));
}
return result;
}
2. 批量插入 – 极大提升数据插入效率
相比单条插入,使用foreach进行批量插入可以减少网络交互次数,显著提高性能。
<insert id="batchInsertUsers">
INSERT INTO users (name, email, age) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age})
</foreach>
</insert>
获取批量插入的自增ID:在MyBatis 3.4.1及以上版本,可以这样配置:
<insert id="batchInsertUsers" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, email) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>
3. 批量更新 – 灵活更新多条记录
MyBatis默认不支持单条SQL执行多个UPDATE语句,需要特殊配置(如连接参数allowMultiQueries=true)。
<update id="batchUpdateUsers">
<foreach collection="users" item="user" separator=";">
UPDATE users
SET name = #{user.name}, email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
4. 动态字段插入 – 智能处理非空字段
结合<trim>和<if>标签,可以实现只插入非空字段的需求。
<insert id="insertSelective">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">name,</if>
<if test="email != null">email,</if>
<if test="age != null">age,</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">#{name},</if>
<if test="email != null">#{email},</if>
<if test="age != null">#{age},</if>
</trim>
</insert>
四、进阶技巧与最佳实践
1. 空集合安全检查
在使用foreach前,务必进行空集合检查,避免生成无效SQL。
<select id="selectByIds" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
2. 复杂参数处理:Map和嵌套对象
当参数复杂时,可以使用Map或嵌套对象作为参数。
// Mapper接口
List<User> selectByComplexCondition(@Param("condition") UserCondition condition);
<select id="selectByComplexCondition" resultType="User">
SELECT * FROM users
WHERE status = #{condition.status}
<if test="condition.ids != null and condition.ids.size() > 0">
AND id IN
<foreach collection="condition.ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
3. 多重循环嵌套
对于复杂业务逻辑,可以使用嵌套的foreach循环。
<insert id="batchInsertRelation">
INSERT INTO user_role (user_id, role_id) VALUES
<foreach collection="userIds" item="userId" separator=",">
<foreach collection="roleIds" item="roleId" separator=",">
(#{userId}, #{roleId})
</foreach>
</foreach>
</insert>
五、性能优化与避坑指南
1. 性能考量
- IN查询长度限制:不同数据库对IN列表长度有限制(如Oracle最多1000项),需要分批处理
- 批量操作大小:单次批量操作不宜过大,一般建议100-1000条记录一批
- 数据库连接配置:批量更新时需要配置
allowMultiQueries=true
2. 常见错误及解决
- SQL语法错误:确保open、close和separator的正确搭配,避免生成如
IN ()这样的无效SQL - 参数绑定问题:正确设置collection属性值,区分list、array和@Param命名的情况
- 空集合处理:始终使用
<if>标签进行空集合检查
3. 安全性注意事项
- SQL注入防护:始终使用
#{}占位符而非${}字符串拼接 - 参数校验:在Java代码中对传入集合进行校验,避免恶意大量数据攻击
MyBatis的foreach标签是处理批量操作和动态SQL的利器,通过熟练掌握其各种用法,可以极大提升开发效率和系统性能。根据实际业务场景选择合适的用法,并注意相关的性能和安全事项,将能使你的数据库操作更加优雅高效。