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 通过以下两种方式实现批量操作:

  1. XML 配置文件中的 <foreach> 标签
  2. @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);
}

注意事项

  1. 参数类型必须是集合:如 List<User>User[]
  2. 数据库兼容性:并非所有数据库支持单条 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 的批处理模式。通过配置 useGeneratedKeyskeyProperty,或直接使用 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 批量插入的起点!

最新发布