Mybatis-Plus 批量插入速度慢的问题优化

MyBatis-Plus 的 batchSave 接口:实现分页批量插入

在实际开发中,批量插入数据是一个常见需求,尤其当数据量较大时,直接使用循环插入效率低下,而 MyBatis-Plus 提供了强大的批量操作支持。本文将详细讲解如何通过配置和代码实现 batchSave 接口的分页批量插入功能,优化性能并避免内存溢出。

1. 背景介绍

MyBatis-Plus 的 BaseMapper 默认提供了 insert 方法,但它只支持单条插入。对于批量插入,通常需要借助 MyBatis 的批量操作能力,而 MP 也支持通过自定义方式实现高效的批量插入。分页批量插入的核心思想是将大数据量分批处理,既保证性能,又避免一次性加载过多数据导致内存压力。


2. 环境准备

依赖配置

确保项目中已引入 MyBatis-Plus 依赖(以 Spring Boot 为例):

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.5.3version>
dependency>

数据库配置

application.yml 中配置数据源和 MyBatis-Plus:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true

3. 实现分页批量插入

MyBatis-Plus 本身没有直接提供 batchSave 接口,我们需要通过自定义 Mapper 和 XML 文件实现分页批量插入功能。

3.1 定义实体类

假设有一个 User 实体类:

@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    // getters and setters
}

3.2 自定义 Mapper 接口

定义一个 UserMapper 接口,添加批量插入方法:

public interface UserMapper extends BaseMapper<User> {
    /**
     * 批量插入用户
     * @param userList 用户列表
     * @return 插入成功的记录数
     */
    int batchInsert(@Param("list") List<User> userList);
}

3.3 配置 XML 文件

src/main/resources/mapper/UserMapper.xml 中实现 batchInsert 的 SQL:


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

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

mapper>

这里使用了 MyBatis 的 标签,动态生成批量插入的 SQL。

3.4 分页批量插入逻辑

在 Service 层实现分页批量插入:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 分页批量插入用户
     * @param userList 待插入的用户列表
     * @param batchSize 每批次插入的数量
     */
    public void batchSaveUsers(List<User> userList, int batchSize) {
        if (userList == null || userList.isEmpty()) {
            return;
        }

        // 计算总批次
        int totalSize = userList.size();
        int batchCount = (totalSize + batchSize - 1) / batchSize;

        for (int i = 0; i < batchCount; i++) {
            // 计算每批的起始和结束索引
            int start = i * batchSize;
            int end = Math.min(start + batchSize, totalSize);

            // 分批截取数据
            List<User> subList = userList.subList(start, end);

            // 调用批量插入
            userMapper.batchInsert(subList);
        }
    }
}

3.5 使用示例

在 Controller 或测试类中调用:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/batchInsert")
    public String batchInsert() {
        // 模拟生成 1000 条数据
        List<User> userList = new ArrayList<>();
        for (int i = 1; i <= 1000; i++) {
            User user = new User();
            user.setName("用户" + i);
            user.setAge(20 + (i % 10));
            userList.add(user);
        }

        // 每批次插入 100 条
        userService.batchSaveUsers(userList, 100);

        return "批量插入完成";
    }
}

4. 优化与注意事项

4.1 性能优化

  • 调整 batchSizebatchSize 不宜过大或过小,通常建议设置为 100-1000,根据数据库性能和数据量调整。
  • 开启 rewriteBatchedStatements:在 MySQL JDBC URL 中添加参数 rewriteBatchedStatements=true,可以将批量插入重写为单条 SQL,提升性能:
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true

4.2 事务管理

对于大批量插入,建议添加事务管理,确保数据一致性:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(rollbackOn = Exception.class)
    public void batchSaveUsers(List<User> userList, int batchSize) {
        // 同上逻辑
    }
}

4.3 错误处理

在实际场景中,可能需要捕获异常并记录失败的数据:

public void batchSaveUsers(List<User> userList, int batchSize) {
    for (int i = 0; i < batchCount; i++) {
        int start = i * batchSize;
        int end = Math.min(start + batchSize, userList.size());
        List<User> subList = userList.subList(start, end);
        try {
            userMapper.batchInsert(subList);
        } catch (Exception e) {
            System.err.println("第 " + (i + 1) + " 批插入失败: " + e.getMessage());
        }
    }
}

5. 验证与测试

运行代码后,可以检查数据库中 t_user 表的数据是否正确插入。如果日志开启,可以看到类似以下的 SQL:

INSERT INTO t_user (name, age) VALUES ('用户1', 20), ('用户2', 21), ...

通过调整 batchSize 和观察执行时间,可以找到最优配置。


6. 总结

通过自定义 batchInsert 接口并结合分页逻辑,我们实现了 MyBatis-Plus 的高效批量插入功能。分页批量插入不仅能处理大数据量,还能有效避免内存溢出问题。结合事务管理和性能优化参数,可以进一步提升其在生产环境中的表现。

希望这篇博文能为你提供实用的参考!如果有其他疑问,欢迎留言讨论。

你可能感兴趣的:(mybatis,mybatis-plus,mysql,sql,java)