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

相关推荐
寻星探路12 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
曹牧14 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
爬山算法15 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty72515 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎15 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄15 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿15 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds16 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化
仟濹16 小时前
【Java基础】多态 | 打卡day2
java·开发语言