MyBatis批量插入生成ID遇问题:foreach内无法使用selectKey
解决MyBatis批量INSERT ALL时序列重复的问题
我完全懂你碰到的这个坑——用MyBatis写INSERT ALL做批量插入时,想给每一行生成唯一ID,但不管是在foreach里尝试用selectKey,还是调用外部的nextVal查询,结果所有行的ID都一模一样。这其实是MyBatis的预编译机制搞的鬼:当你把序列查询当成参数引用时,MyBatis只会执行一次这个查询,然后把结果重复填充到所有行里。
下面给你几个实用的解决方案:
方案1:直接在INSERT ALL的VALUES里调用序列
最省心的办法就是跳过MyBatis的参数绑定,直接在SQL里调用序列的nextVal,让数据库为每一行单独生成新的序列值。修改你的insertAll方法:
<insert id="insertAll" parameterType="list"> INSERT ALL <foreach collection="items" item="item" separator=""> INTO MY_TABLE (ID, COLUMN1, COLUMN2) VALUES (MY_SEQUENCE.nextVal, #{item.column1}, #{item.column2}) </foreach> SELECT 1 FROM DUAL </insert>
注意Oracle的INSERT ALL语法最后必须加SELECT 1 FROM DUAL来触发执行,这样每一行都会独立调用一次序列,保证ID唯一。
方案2:提前在Java代码中批量生成序列值
如果必须通过MyBatis获取序列值,可以先一次性查询出足够的序列值,分配给每个待插入对象后再执行批量插入:
- 先写一个批量获取序列的Mapper方法:
<select id="batchNextVal" resultType="java.lang.Long"> SELECT MY_SEQUENCE.nextVal FROM DUAL CONNECT BY LEVEL <= #{count} </select>
这个Oracle语法的SQL会一次性生成count个连续的序列值。
- 在业务代码中先拿序列值,再给每个对象赋值:
// list是你要插入的对象集合 List<Long> ids = mapper.batchNextVal(list.size()); for (int i = 0; i < list.size(); i++) { list.get(i).setId(ids.get(i)); } mapper.insertAll(list);
- 最后修改
insertAll的SQL,直接用对象里的ID:
<insert id="insertAll" parameterType="list"> INSERT ALL <foreach collection="items" item="item" separator=""> INTO MY_TABLE (ID, COLUMN1, COLUMN2) VALUES (#{item.id}, #{item.column1}, #{item.column2}) </foreach> SELECT 1 FROM DUAL </insert>
方案3:改用标准批量插入语法(foreach+INSERT INTO...VALUES)
如果你的Oracle版本是12c及以上,可以不用INSERT ALL,换成标准的批量插入写法,MyBatis能正确处理每个行的序列调用:
<insert id="insertAll" parameterType="list"> INSERT INTO MY_TABLE (ID, COLUMN1, COLUMN2) VALUES <foreach collection="items" item="item" separator=","> (MY_SEQUENCE.nextVal, #{item.column1}, #{item.column2}) </foreach> </insert>
这种写法更简洁,而且每个VALUES组都会独立触发序列调用,不会出现重复ID的问题。
总结一下,最推荐方案1,直接在SQL里调用序列,简单高效,不需要额外的Java代码处理;如果有特殊业务限制,再考虑方案2或3。
内容的提问来源于stack exchange,提问作者Manuel Castro
相关产品推荐
相关产品推荐

