浅说MyBatis-Plus 的 saveBatch 方法

发布于:2025-05-13 ⋅ 阅读:(13) ⋅ 点赞:(0)

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

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

    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循环插入:

    //普通的增加十万条数据
    @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()方法分批次插入

    //批量插入,一次插入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()方法只有这点神通嘛?

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

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

//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));
    }

继续深入

    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.sqlSessionFactory, this.log, list, batchSize, consumer);
    }
//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 …

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

rewriteBatchedStatements=true

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

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,性能大大提升!


网站公告

今日签到

点亮在社区的每一天
去签到