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

相关推荐
C+-C资深大佬16 小时前
C++多态
java·jvm·c++
WJX_KOI16 小时前
保姆级教程:Apache Seatunnel CDC(standalone 模式)部署 MySQL CDC、PostgreSQL CDC 及使用方法
java·大数据·mysql·postgresql·big data·etl
今天你TLE了吗16 小时前
JVM学习笔记:第一章——JVM&Java体系结构
java·jvm·笔记·学习
毕设源码-朱学姐16 小时前
【开题答辩全过程】以 基于JAVAweb的影视创作论坛系统为例,包含答辩的问题和答案
java
葡萄城技术团队16 小时前
GcExcel V9.0 新特性解密:VALUETOTEXT/ARRAYTOTEXT 双函数,让数据文本转换更精准高效
java
她说..16 小时前
策略模式+工厂模式实现订单校验功能
java·spring boot·java-ee·简单工厂模式·策略模式
短剑重铸之日16 小时前
《设计模式》第五篇:策略模式
java·后端·设计模式·策略模式
知行合一。。。17 小时前
Linux--10---crontab -e定时任务
java·linux·运维
cyforkk17 小时前
16、Java 基础硬核复习:网络编程的核心逻辑与面试考点
java·网络·面试
serve the people17 小时前
python环境搭建 (五) Dockerfile 和 docker-compose.yml 核心作用
java·python·docker