2200 字
约 7 分钟
1
第42篇-MyBatis批量插入

第42篇-MyBatis批量插入

来源: https://bugstack.cn/md/java/interview/2026-05-26-面经手册 · 第42篇《MyBatis 批量插入有几种方式?自增主键怎么获取?多参数怎么传?》.html


面经手册 · 第42篇《MyBatis 批量插入有几种方式?自增主键怎么获取?多参数怎么传?》

作者:小傅哥 **博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!😄

一、前言

实际项目中,批量操作是绕不开的话题。单条 INSERT 循环插入 1000 条数据,性能慢到让人怀疑人生。 MyBatis 提供了多种批量操作方式,多参数传递也有多种手段。面试不仅问"有几种方式",还会追问"为什么 Batch Executor 快"、"@Param 的原理是什么"。

二、面试题

谢飞机,小记!,面试继续。 面试官**:MyBatis 批量插入有几种方式?

谢飞机:foreach 拼接 SQL,还有 Batch Executor。

面试官:还有吗?

谢飞机:还有……MySQL 的 LOAD DATA?

面试官:对。那 Batch Executor 为什么快?

谢飞机:预编译复用?

面试官:预编译复用是 ReuseExecutor 的特点,Batch Executor 快在哪里?

谢飞机:缓存 SQL 语句,批量提交?

面试官:那自增主键怎么获取?

谢飞机:useGeneratedKeys="true" keyProperty="id"

面试官:原理是什么?底层调用了 JDBC 的什么方法?

谢飞机:getGeneratedKeys()?

面试官:对。那批量插入时,能返回所有自增主键吗?

谢飞机:这个……不知道。再见!ヾ( ̄▽ ̄)

三、批量插入三种方式

1. 方式一:foreach 拼接 SQL


insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO user(name, age, email) VALUES
    foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email})
    foreach>
insert>

12 3 4 5 6 7 执行 SQL

INSERT INTO user(name, age, email)
VALUES ('张三', 18, 'zhangsan@test.com'),
       ('李四', 20, 'lisi@test.com'),
       ('王五', 22, 'wangwu@test.com')

12 3 4 优点:一条 SQL,性能好,MyBatis 层面最简单 缺点:SQL 长度受 max_allowed_packet 限制,需要分批

分批处理

// 每批 500 条
int batchSize = 500;
for (int i = 0; i  userList.size(); i += batchSize) {
    ListUser> batch = userList.subList(i,
        Math.min(i + batchSize, userList.size()));
    userMapper.batchInsert(batch);
}

1**2 3 4 5 6 7

2. 方式二:Batch Executor

// 手动使用 Batch Executor
SqlSession sqlSession = sqlSessionFactory
    .openSession(ExecutorType.BATCH, false);

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

try {
    for (User user : userList) {
        mapper.insert(user);
    }
    sqlSession.commit();  // 批量提交
} finally {
    sqlSession.close();
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 原理**:Batch Executor 将多个 INSERT/UPDATE/DELETE 语句缓存,在 commit() 时一次性发送给数据库执行,减少网络往返次数。

优点:真正减少网络 IO,适合超大批量 缺点:需要手动管理 SqlSession,和 Spring 整合需要特殊处理

3. 方式三:数据库批量语法

-- MySQL:LOAD DATA
LOAD DATA LOCAL INFILE '/tmp/users.csv'
INTO TABLE user
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(name, age, email);

12 3 4 5 6 优点:性能最高,适合千万级数据导入 缺点:数据库相关,不通用,需要处理文件

4. 三种方式对比

对比项 foreach 拼接 Batch Executor LOAD DATA
性能 最高
通用性 差(数据库相关)
代码复杂度
SQL 长度限制
Spring 整合 简单 需特殊处理 需特殊处理
推荐场景 日常批量(insert id="insert" parameterType="com.example.entity.User"
    useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})

insert>



1**2
3
4

User user = new User(); user.setName("张三"); user.setAge(18); user.setEmail("zhangsan@test.com");

userMapper.insert(user); // 插入后,自增主键自动回填到 user.id System.out.println(user.getId()); // 输出:自增 ID


 1
2
3
4
5
6
7
8

### 2. selectKey 方式(非自增主键)



insert id="insert" parameterType="com.example.entity.User"> selectKey keyProperty="id" resultType="long" order="BEFORE"> SELECT seq_user.nextval FROM dual

selectKey>
INSERT INTO user(id, name, age) VALUES(#{id}, #{name}, #{age})

insert>


 1
2
3
4
5
6
7
8
`order="BEFORE"`:先执行 selectKey,再执行 INSERT
`order="AFTER"`:先执行 INSERT,再执行 selectKey(自增主键场景)

### 3. 源码追踪

 Jdbc3KeyGenerator**:

// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator public class Jdbc3KeyGenerator implements KeyGenerator {

@Override
public void processAfter(Executor executor, MappedStatement ms,
        Statement stmt, Object parameter) {
    // 核心:调用 JDBC 的 getGeneratedKeys()
    ResultSet rs = stmt.getGeneratedKeys();

    if (rs != null) {
        // 遍历结果集,回填主键到参数对象
        ListObject> keys = new ArrayList>();
        while (rs.next()) {
            keys.add(rs.getObject(1));
        }
        // 通过 MetaObject 反射设置 keyProperty
        MetaObject metaObject = configuration.newMetaObject(parameter);
        metaObject.setValue(keyProperty, keys.get(0));
    }
}

}



1**2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
调用时机**:`StatementHandler.update()` 执行完 INSERT 后,调用 `keyGenerator.processAfter()`。

### 4. 批量插入返回主键



insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user(name, age) VALUES foreach collection="list" item="user" separator=","> (#{user.name}, #{user.age}) foreach> insert>



1**2
3
4
5
6
7
8

// 批量插入后,每个 User 对象的 id 都会被回填 userMapper.batchInsert(userList); for (User user : userList) { System.out.println(user.getId()); // 每个都有自增 ID }


 1
2
3
4
5
注意**:不同数据库对批量插入返回自增主键的支持不同,MySQL 支持,Oracle 需要用 selectKey。

## 五、多参数传递



### 1. 方式一:@Param 注解(推荐)



ListUser> findByCondition( @Param("name") String name, @Param("age") Integer age );



1**2
3
4

select id="findByCondition" resultType="com.example.entity.User"> SELECT * FROM user WHERE name = #{name} AND age = #{age} select>


 1
2
3
4

### 2. 方式二:Map 传参



MapString, Object> params = new HashMap>(); params.put("name", "张三"); params.put("age", 18); userMapper.findByCondition(params);


 1
2
3
4

select id="findByCondition" parameterType="map" resultType="com.example.entity.User"> SELECT * FROM user WHERE name = #{name} AND age = #{age} select>


 1
2
3
4
5

### 3. 方式三:JavaBean / DTO 传参



UserQuery query = new UserQuery(); query.setName("张三"); query.setAge(18); userMapper.findByCondition(query);


 1
2
3
4

select id="findByCondition" parameterType="com.example.entity.UserQuery" resultType="com.example.entity.User"> SELECT * FROM user WHERE name = #{name} AND age = #{age} select>


 1
2
3
4
5

### 4. 方式四:索引参数(不推荐)



// 不用 @Param,MyBatis 自动命名为 param1, param2... 或 arg0, arg1... ListUser> findByCondition(String name, Integer age);


 1
2

select id="findByCondition" resultType="com.example.entity.User"> SELECT * FROM user WHERE name = #{param1} AND age = #{param2} select>


 1
2
3
4
5

### 5. @Param 原理



// org.apache.ibatis.reflection.ParamNameResolver public class ParamNameResolver {

public Object getNamedParams(Object[] args) {
    final int paramCount = params.size();
    if (args == null || paramCount == 0) return null;

    if (!hasParamAnnotation && paramCount == 1) {
        return args[0];  // 只有一个参数,直接返回
    }

    // 构建参数名 → 参数值的 Map
    final MapString, Object> param = new MapperMethod.ParamMap>();
    for (int i = 0; i  paramCount; i++) {
        if (hasParamAnnotation) {
            param.put(paramAnnotations.get(i).value(), args[i]);  // @Param 的值
        } else {
            param.put("param" + (i + 1), args[i]);  // param1, param2...
        }
    }
    return param;
}

}


 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
结论**:`@Param("name")` 的本质是把参数名和参数值放入一个 Map,XML 中通过 `#{name}` 从 Map 中取值。

### 6. 四种方式对比



| 方式 | 优点 | 缺点 | 推荐场景 |
| --- | --- | --- | --- |
| @Param | 简单直观,类型安全 | 参数多时长方法签名 | 参数  4194304).
You can change this value on the server by setting max_allowed_packet

12 解决:分批插入,每批 500-1000 条。

2. 性能优化


insert id="batchInsert" parameterType="java.util.List" flushCache="true">
    INSERT INTO user(name, age) VALUES
    foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age})
    foreach>
insert>

1**2 3 4 5 6 7

七、常见面试追问

Q1:Batch Executor 和 foreach 批量插入哪个快?

取决于数据量: 数据量小( 1000):Batch Executor 减少网络 IO,更快 超大数据(> 10000):LOAD DATA 或分批 foreach

Q2:批量插入时怎么返回所有自增主键?

MySQL 中 foreach 批量插入 + useGeneratedKeys="true" 可以返回所有自增 ID。MyBatis 会调用 PreparedStatement.getGeneratedKeys() 获取所有生成的主键。

Q3:@Param 和不加 @Param 有什么区别?

不加 @Param 时,MyBatis 用 param1, param2...arg0, arg1... 命名参数,XML 中必须用这些名字。加了 @Param("name") 后,可以用 #{name} 引用,可读性更好。

八、总结

记住三个核心要点:

1. 批量插入三种方式
   foreach 拼接:一条 SQL,简单,注意 max_allowed_packet
   Batch Executor:减少网络 IO,需手动管理 SqlSession
   LOAD DATA:性能最高,适合数据导入

2. 自增主键获取
   useGeneratedKeys="true" keyProperty="id"(推荐)
   底层:Jdbc3KeyGenerator.processAfter() → stmt.getGeneratedKeys()
   批量插入也能返回所有自增 ID(MySQL 支持)

3. 多参数传递四种方式
   @Param 注解(推荐,参数少时)
   Map 传参(灵活但无类型检查)
   JavaBean/DTO(类型安全,参数多时)
   索引参数(不推荐)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 面试回答模板**:

MyBatis 批量插入有三种方式:foreach 拼接 SQL(一条 SQL 插入多行,简单但要注意 max_allowed_packet 限制)、Batch Executor(减少网络 IO,适合大批量)、数据库批量语法如 LOAD DATA(性能最高,适合数据导入)。 获取自增主键用 useGeneratedKeys="true" keyProperty="id",底层是 Jdbc3KeyGenerator 调用 JDBC 的 getGeneratedKeys() 获取自增 ID 并反射回填到参数对象。批量插入时 MySQL 也支持返回所有自增主键。 多参数传递推荐用 @Param 注解,原理是 ParamNameResolver 把参数名和值放入 Map,XML 中通过 #{name} 从 Map 取值。参数多时推荐用 JavaBean/DTO。

第42篇-MyBatis批量插入
http://www.clxhxhhr.top/posts/987/
作者
clxstart
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。