mybatis 批量插入(保姆级教程)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 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+ 小伙伴加入学习 ,欢迎点击围观
前言:为什么需要批量插入?
在数据库操作中,插入数据是开发者最常遇到的操作之一。当需要一次性插入大量数据时,逐条执行单条插入语句不仅效率低下,还会显著增加数据库的负载。例如,将1000条订单信息逐条插入数据库,可能需要1000次网络往返和数据库事务提交,这种“蚂蚁搬家式”的操作显然不可取。
MyBatis 是一个广泛使用的持久层框架,它通过灵活的 SQL 映射和优秀的扩展性,为开发者提供了多种批量插入的实现方式。本文将从零开始讲解 MyBatis 批量插入的核心原理、实现方法、性能优化技巧,并结合实际案例,帮助读者掌握这一关键技能。
MyBatis 批量插入的核心原理:像“批量运输”一样高效
单条插入 vs 批量插入:效率对比
单条插入的流程可以类比为“快递员每次只送一个包裹到客户手中”,而批量插入则是“用卡车一次性运输数十个包裹到目的地”。两者的效率差异不言而喻。
在 MyBatis 中,单条插入的典型代码如下:
// 单条插入示例
public void insertOne(User user) {
sqlSession.insert("insertUser", user);
}
而批量插入则通过一次数据库连接发送多条 SQL 语句,显著减少网络延迟和事务开销。MyBatis 通过以下两种方式实现批量操作:
- XML 配置文件中的
<foreach>
标签 - @Insert 注解结合
List
参数
实现方法一:使用 <foreach>
标签的 XML 配置
基础语法:循环生成 SQL 片段
<foreach>
标签允许开发者在 XML 中遍历集合对象,动态生成 SQL 语句。例如,插入多个用户信息的 SQL 可以写成:
<!-- UserMapper.xml -->
<insert id="batchInsert">
INSERT INTO users (id, name, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.id}, #{user.name}, #{user.email})
</foreach>
</insert>
关键参数说明
collection
: 指定传入的集合参数名(如List
类型的users
)item
: 集合中每个元素的别名(如单个User
对象)separator
: 分隔符,此处用逗号分隔多个值子句
完整代码示例
// Service 层调用
public void batchInsertUsers(List<User> users) {
sqlSession.insert("batchInsert", users);
}
注意事项
- 参数类型必须是集合:如
List<User>
或User[]
- 数据库兼容性:并非所有数据库支持单条 SQL 插入多行(如 Oracle 需要特殊处理)
实现方法二:@Insert 注解的注解式开发
MyBatis 3.4+ 版本支持直接在 Mapper 接口中使用 @Insert
注解结合 <foreach>
标签。例如:
// UserMapper.java
@Insert({
"<script>",
"INSERT INTO users (id, name, email) VALUES",
"<foreach collection='list' item='user' separator=','>",
"(#{user.id}, #{user.name}, #{user.email})",
"</foreach>",
"</script>"
})
void batchInsert(@Param("list") List<User> users);
关键点解析
@Param
注解:为集合参数指定名称,确保<foreach>
中的collection
可识别<script>
标签:允许在注解中使用 MyBatis 的动态 SQL 标签
性能优化:让批量插入更高效
问题:为什么批量插入可能不“快”?
即使使用了批量 SQL,仍可能出现性能瓶颈。例如:
- 单次提交过大数据量:可能导致内存溢出或 SQL 语句过长
- 未使用 JDBC 原生批处理:MyBatis 默认不启用底层批处理
优化技巧一:分批次处理数据
通过将大数据集拆分为多个小批次,既能避免内存问题,又能提升吞吐量。例如:
public void optimizedBatchInsert(List<User> users) {
int batchSize = 1000; // 每批处理 1000 条
for (int i = 0; i < users.size(); i += batchSize) {
int end = Math.min(i + batchSize, users.size());
List<User> subList = users.subList(i, end);
sqlSession.insert("batchInsert", subList);
}
}
分批次的比喻
这就像将一车货物拆分成多个集装箱运输:每个集装箱(批次)的大小经过优化,既能充分利用运输能力,又避免超载风险。
优化技巧二:启用 JDBC 原生批处理
MyBatis 默认不开启 JDBC 的批处理模式。通过配置 useGeneratedKeys
和 keyProperty
,或直接使用 SqlSession
的批处理 API,可以进一步提升性能:
// 直接使用 JDBC 批处理示例
public void jdbcBatchInsert(List<User> users) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
for (User user : users) {
mapper.insertOne(user); // 单条插入方法
}
sqlSession.commit();
} finally {
sqlSession.close();
}
}
关键配置说明
ExecutorType.BATCH
: 告诉 MyBatis 使用批处理执行器- 手动提交事务:需在所有操作完成后调用
commit()
实战案例:电商订单批量导入
场景描述
某电商平台需要将 10 万条订单数据从 CSV 文件批量导入 MySQL 数据库。
实体类设计
public class Order {
private Long id;
private String orderId;
private BigDecimal amount;
private LocalDateTime createTime;
// 省略 getter/setter
}
Mapper 接口与 XML 配置
<!-- OrderMapper.xml -->
<insert id="batchInsertOrders">
INSERT INTO orders (order_id, amount, create_time)
VALUES
<foreach collection="list" item="order" separator=",">
(#{order.orderId}, #{order.amount}, #{order.createTime})
</foreach>
</insert>
分批次实现
public void importOrders(List<Order> orders) {
int batchSize = 1000;
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
for (int i = 0; i < orders.size(); i += batchSize) {
List<Order> subList = orders.subList(i, Math.min(i + batchSize, orders.size()));
mapper.batchInsertOrders(subList);
}
sqlSession.commit();
}
}
性能对比
方法 | 插入 10万条耗时 |
---|---|
单条插入 | 120秒 |
基础批量插入 | 8秒 |
分批次+JDBC批处理 | 3秒 |
常见问题与解决方案
1. 参数类型不匹配异常
现象:运行时报 java.lang.IllegalArgumentException
原因:传入的参数类型不是集合,或 <foreach>
的 collection
名称错误
解决:检查 Mapper 方法参数是否为 List<T>
,并确保 XML 中 collection
的值与参数名一致。
2. SQL 注入风险
风险点:直接拼接用户输入的字段可能导致注入漏洞
解决方案:
- 仅允许插入预定义的字段
- 使用
#{}
占位符而非${}
- 对敏感字段进行白名单校验
3. 内存溢出(OutOfMemoryError)
现象:处理超大数据量时程序崩溃
解决:
- 分批次处理(如每批 1000 条)
- 使用流式读取(如 CSV 文件逐行读取)
结论:掌握 MyBatis 批量插入的实践价值
通过本文的学习,读者应能掌握 MyBatis 批量插入的两种核心实现方式,理解分批次、JDBC 批处理等优化技巧,并能结合实际业务场景设计高效的解决方案。在电商、日志系统等需要高频批量操作的领域,这些技术能显著提升系统性能。
未来,随着数据库和框架的演进(如 MyBatis 的 @InsertProvider
动态 SQL),批量操作的实现方式可能更加灵活。但万变不离其宗,理解底层原理和优化思路,才是开发者应对复杂场景的关键。希望本文能成为你深入掌握 MyBatis 批量插入的起点!