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

相关推荐
我喜欢山,也喜欢海33 分钟前
Jenkins Maven 带权限 搭建方案2025
java·jenkins·maven
明天更新39 分钟前
Java处理压缩文件的两种方式!!!!
java·开发语言·7-zip
铁锚1 小时前
一个WordPress连续登录失败的问题排查
java·linux·服务器·nginx·tomcat
yychen_java1 小时前
上云API二开实现三维可视化控制中心
java·无人机
理智的煎蛋1 小时前
keepalived+lvs
java·开发语言·集成测试·可用性测试
CopyLower1 小时前
Java与AI技术结合:从机器学习到生成式AI的实践
java·人工智能·机器学习
生命不息战斗不止(王子晗)1 小时前
mybatis中${}和#{}的区别
java·服务器·tomcat
.生产的驴1 小时前
Docker 部署Nexus仓库 搭建Maven私服仓库 公司内部仓库
java·运维·数据库·spring·docker·容器·maven
橙子199110162 小时前
Kotlin 中的 Unit 类型的作用以及 Java 中 Void 的区别
java·开发语言·kotlin
yours_Gabriel2 小时前
【登录认证】JWT令牌
java·开发语言·redis