springBoot+mybatisPlus批量插入,千万数据导入,真实可用

本文介绍了如何在SpringBoot项目中利用MybatisPlus进行批量数据插入,适用于大规模数据迁移场景。通过创建EasySqlInjector、自定义mapper接口、批量插入实现类以及线程池配置,实现了高效的数据导入。测试表明,2000条数据一个SQL较为合适,可根据实际情况调整参数以应对SQL超长问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     java开发springBoot+mybatisPlus已经成为主流,最近遇到新旧系统的切换,批量插入读取原来的数据插入到新的系统的表中,使用mybatisPlus的方法

saveBatch(),但是发现巨慢,查询源码和sql发现其实里面也是遍历之后再单条插入,难怪慢呢.其实就是伪批量,可能再事务提交上有处理,但是还是不能容忍.查资料整理出来一套正式的批量插入方法,生产已经使用,实测10W数据.1-3分钟左右可插入,完全可以满足需求,有需要的小伙伴可以参考,简简单单集成到自己的系统,废话不多说,直接上代码:

第一步:创建 EasySqlInjector到spring容器

@Bean
public EasySqlInjector easySqlInjector() {
    return new EasySqlInjector();
}

第二部:创建自定义mapper接口

自定义 mapper接口,扩展BaseMapper,并使用mybatis的真实批量插入方法
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.Collection;

/**
 * @description:批量插入
 * @author: xxx
 * @create: 2020-12-21 14:27
 **/
public interface EasyBaseMapper<T> extends BaseMapper<T> {
    /**
     * 批量插入
     *
     * @param entityList 实体列表
     * @return 影响行数
     */
    Integer insertBatchSomeColumn(Collection<T> entityList);
}

第三步:创建批量插入的实现类  BaseIServiceImpl

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @description:批量插入的方法
 
 **/
@Service
@Slf4j
public class BaseIServiceImpl<M, T> {

    @Autowired
    @Qualifier(value = "defaultThreadPoolExecutor")
    private ThreadPoolExecutor threadPoolExecutor;


    /**
     * 开启多线程的插入,size单次sql大小
     *
     * @param entityList
     * @param mapper
     * @param size
     * @return
     */
    public Integer insertBatchSomeColumnByThread(List<T> entityList, Class<M> mapper, int size) {
        EasyBaseMapper easyBaseMapper = (EasyBaseMapper) SpringContextUtils.getBean(mapper);
        //返回结果
        AtomicReference<Integer> resultInteger = new AtomicReference<>(0);
        //数据总长度
        int length = entityList.size();
        // 余数
        int r = length % size;
        // 截取次数
        int index = length / size;
        // 如果余数大于0,截取次数+1
        if (r > 0) {
            index = index + 1;
        }
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < index; i++) {
            // 注意下标越界的问题,尤其最后一轮截取
            List<T> subSaveList = null;
            if (r > 0 && i == index - 1) {
                subSaveList = entityList.subList(i * size, length);
            } else {
                subSaveList = entityList.subList(i * size, (i + 1) * size);
            }

            List<T> finalSubSaveList = subSaveList;
            threadPoolExecutor.execute(() -> {
                        log.info("当前执行线程" + Thread.currentThread().getName());
                        Integer integer = easyBaseMapper.insertBatchSomeColumn(finalSubSaveList);
                        resultInteger.set(integer + resultInteger.get());
                    }
            );
        }
        long endTime = System.currentTimeMillis();
        log.info(mapper.getName() + "数据总长度," + length + " 多线程插入用时" + (endTime - startTime) + " ms");
        return resultInteger.get();
    }

    /**
     * 开启多线程的插入,size单次sql大小,默认1000
     *
     * @param entityList
     * @param mapper
     * @return
     */
    public Integer insertBatchSomeColumnByThread(List<T> entityList, Class<M> mapper) {
        return insertBatchSomeColumnByThread(entityList, mapper, 1000);
    }

    /**
     * 批量插入,size单次sql大小,默认1000
     *
     * @param entityList
     * @param mapper
     * @return
     */
    public Integer insertBatchSomeColumn(List<T> entityList, Class<M> mapper) {
        return insertBatchSomeColumn(entityList, mapper, 1000);
    }

    /**
     * 批量插入,size单次sql大小
     *
     * @param entityList
     * @param mapper
     * @param size
     * @return
     */
    public Integer insertBatchSomeColumn(List<T> entityList, Class<M> mapper, int size) {
        EasyBaseMapper easyBaseMapper = (EasyBaseMapper) SpringContextUtils.getBean(mapper);
        //返回结果
        Integer resultInteger = 0;
        //数据总长度
        int length = entityList.size();
        // 余数
        int r = length % size;
        // 截取次数
        int index = length / size;
        // 如果余数大于0,截取次数+1
        if (r > 0) {
            index = index + 1;
        }
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < index; i++) {
            // 注意下标越界的问题,尤其最后一轮截取
            if (r > 0 && i == index - 1) {
                resultInteger += easyBaseMapper.insertBatchSomeColumn(entityList.subList(i * size, length));
            } else {
                resultInteger += easyBaseMapper.insertBatchSomeColumn(entityList.subList(i * size, (i + 1) * size));
            }
        }
        long endTime = System.currentTimeMillis();
        log.info(mapper.getName() + "数据总长度," + length + " 插入用时" + (endTime - startTime) + " ms");
        return resultInteger;
    }
}
SpringContextUtils 工具包 
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public SpringContextUtils() {
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<?> getType(String name) {
        return applicationContext.getType(name);
    }

线程池配置 ThreadPoolExecutor

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class ThreadPoolExecutorConfig {

  
    @Bean(name = "defaultThreadPoolExecutor")
    public ThreadPoolExecutor threadPoolExecutor() {
        return new ThreadPoolExecutor(20, 40, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    }

第五步:测试使用

mapper接口集成EasyBaseMapper

注入

@Autowired
private BaseIServiceImpl baseIService;
xxxMapper:自己的mapper接口
baseIService.insertBatchSomeColumnByThread(list, xxxMapper.class);

参数可调整,视自己的单个数据的sql长度确定.一般2000条数据一个sql比较合理,单个sql较长可配置1000,默认1000,如果参数不可能可能报错,sql超长的异常,调整参数即可.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值