浅说MyBatis-Plus 的 saveBatch 方法

MyBatis-Plus 的 saveBatch 方法是 ORM 框架中批量插入的核心功能,理解其实现原理和优化技巧对开发高性能应用至关重要。

在我们的userServiceTest类中定义一个插入数据的方法:

java 复制代码
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user"+i);
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

现在,想要往数据库里面插入10万条数据,现在有两种实现方法:

第一种:使用for循环插入:

java 复制代码
    //普通的增加十万条数据
    @Test
    public void TestOneByOne(){
        //记录开始时间
        long startTime = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            userService.save(buildUser(i));
        //记录结束时间
        long endTime = System.currentTimeMillis();
            //总耗时
            System.out.println("总耗时:" + (endTime - startTime) + "ms");
        }
    }

耗时:10min(因为电脑性能原因不方便演示,之前试过一次)

第二种:使用MybatisPlus里面Service提供的saveBatch()方法分批次插入

java 复制代码
    //批量插入,一次插入1000条数据,总共插入10万条数据
    @Test
    public void TestBatchInsert(){
        List<User> userList = new ArrayList<>(1000);
        //记录开始时间
        long startTime = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            userList.add(buildUser(i));
            if(i % 1000 == 0){
                userService.saveBatch(userList);
                //清空集合
                userList.clear();
            }
        }
        //记录结束时间
        long endTime = System.currentTimeMillis();
        //总耗时
        System.out.println("总耗时:" + (endTime - startTime) + "ms");
    }

结果:十万条数据成功插入,总耗时约43s

但是,我们不禁思考,Service提供的saveBatch()方法只有这点神通嘛?

答案是否定的,我们可以让他更快!

那么如何做呢?让我们进入到其中的源码部分

java 复制代码
//ServiceImpl.java
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

继续深入

java 复制代码
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.sqlSessionFactory, this.log, list, batchSize, consumer);
    }
java 复制代码
//SqlHelper.java
    public static <E> boolean executeBatch(SqlSessionFactory sqlSessionFactory, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        return !CollectionUtils.isEmpty(list) && executeBatch(sqlSessionFactory, log, sqlSession -> {
            int size = list.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            for (E element : list) {
                consumer.accept(sqlSession, element);
                if (i == idxLimit) {
                    sqlSession.flushStatements();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
                i++;
            }
        });
    }

我们会发现,他的底层居然是依靠for循环一个一个插入,并不是我们想象中的类似insert into 表名 valus ...

此处,如果你需要这样做,你需要去开启一个参数:

java 复制代码
rewriteBatchedStatements=true

无优化时 ​:生成多条INSERT语句(INSERT INTO ... VALUES (...)),等同于第一种普通for循环,一条一条插入的做法,性能低下

优化后 ​:合并为单条批量语法(INSERT INTO ... VALUES (...),(...),...

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true//已开启
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

加入后继续执行

总耗时:约12s,性能大大提升!

相关推荐
李少兄3 小时前
从原理到实战:Spring IoC/DI 核心知识体系与高频面试题全解
java·后端·spring
飞天狗1113 小时前
零基础JavaWeb入门——第五课第二小节:九大内置对象 · 第2个:response(响应对象)
java·开发语言
许彰午3 小时前
39_Java单元测试JUnit入门
java·junit·单元测试
shushangyun_3 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
JAVA9653 小时前
JAVA面试-JVM篇 03-JVM运行时数据区哪些是线程私有的哪些是共享的
java·jvm·面试
于先生吖3 小时前
教育类Java实战项目:在线错题整理平台分层架构设计与接口源码解析
java·开发语言
慧一居士3 小时前
Feign的GET请求如何传递对象参数?
java·spring cloud
开发小能手-roy4 小时前
Java集合框架选型指南:从ArrayList到ConcurrentSkipListMap
java·开发语言
凡人叶枫4 小时前
Effective C++ 条款41:了解隐式接口和编译期多态
java·开发语言·c++·effective c++
凡人叶枫4 小时前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++