百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

项目案例:Java多线程批量拆分List导入数据库

wptr33 2025-03-03 20:29 10 浏览

  • 一、前言
  • 二、直接把list怼进Mysql
  • 三、分组把list导入Mysql中
  • 四、多线程分批导入Mysql
  • 五、小结



一、前言

前两天做了一个导入的功能,导入开始的时候非常慢,导入2w条数据要1分多钟,后来一点一点的优化,从直接把list怼进Mysql中,到分配把list导入Mysql中,到多线程把list导入Mysql中。时间是一点一点的变少了。非常的爽,最后变成了10s以内。下面就展示一下过程。

二、直接把list怼进Mysql

使用mybatis的批量导入操作:

  @Transactional(rollbackFor = Exception.class)
    public int addFreshStudentsNew2(List list, String schoolNo) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        List studentEntityList = new LinkedList<>();
        List enrollStudentEntityList = new LinkedList<>();
        List allusersEntityList = new LinkedList<>();

        for (FreshStudentAndStudentModel freshStudentAndStudentModel : list) {

            EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
            StudentEntity studentEntity = new StudentEntity();
            BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
            BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
            String operator = TenancyContext.UserID.get();
            String studentId = BaseUuidUtils.base58Uuid();
            enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
            enrollStudentEntity.setStudentId(studentId);
            enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            enrollStudentEntity.setOperator(operator);
            studentEntity.setId(studentId);
            studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            studentEntity.setOperator(operator);
            studentEntityList.add(studentEntity);
            enrollStudentEntityList.add(enrollStudentEntity);

            AllusersEntity allusersEntity = new AllusersEntity();
            allusersEntity.setId(enrollStudentEntity.getId());
            allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
            allusersEntity.setUserName(enrollStudentEntity.getName());
            allusersEntity.setSchoolNo(schoolNo);
            allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
            allusersEntity.setPassword(enrollStudentEntity.getNemtCode());  //密码设置为考生号
            allusersEntityList.add(allusersEntity);
        }
            enResult = enrollStudentDao.insertAll(enrollStudentEntityList);
            stuResult = studentDao.insertAll(studentEntityList);
            allResult = allusersFacade.insertUserList(allusersEntityList);

        if (enResult > 0 && stuResult > 0 && allResult) {
            return 10;
        }
        return -10;
    }

Mapper.xml

  
        insert into tb_enroll_student
        
                id,  
                remark,  
                nEMT_aspiration,  
                nEMT_code,  
                nEMT_score,  
                student_id,  
                identity_card_id,  
                level,  
                major,  
                name,  
                nation,  
                secondary_college,  
                operator,  
                sex,  
                is_delete,  
                account_address,  
                native_place,  
                original_place,  
                used_name,  
                pictrue,  
                join_party_date,  
                political_status,  
                tel_num,  
                is_registry,  
                graduate_school,  
                create_time,  
                update_time                
        values
        
        (
                #{item.id,jdbcType=VARCHAR},
                #{item.remark,jdbcType=VARCHAR},
                #{item.nemtAspiration,jdbcType=VARCHAR},
                #{item.nemtCode,jdbcType=VARCHAR},
                #{item.nemtScore,jdbcType=VARCHAR},
                #{item.studentId,jdbcType=VARCHAR},
                #{item.identityCardId,jdbcType=VARCHAR},
                #{item.level,jdbcType=VARCHAR},
                #{item.major,jdbcType=VARCHAR},
                #{item.name,jdbcType=VARCHAR},
                #{item.nation,jdbcType=VARCHAR},
                #{item.secondaryCollege,jdbcType=VARCHAR},
                #{item.operator,jdbcType=VARCHAR},
                #{item.sex,jdbcType=VARCHAR},
                0,
                #{item.accountAddress,jdbcType=VARCHAR},
                #{item.nativePlace,jdbcType=VARCHAR},
                #{item.originalPlace,jdbcType=VARCHAR},
                #{item.usedName,jdbcType=VARCHAR},
                #{item.pictrue,jdbcType=VARCHAR},
                #{item.joinPartyDate,jdbcType=VARCHAR},
                #{item.politicalStatus,jdbcType=VARCHAR},
                #{item.telNum,jdbcType=VARCHAR},
                #{item.isRegistry,jdbcType=TINYINT},
                #{item.graduateSchool,jdbcType=VARCHAR},
                now(),
                now()        
        )   
                        
   

代码说明:

底层的mapper是通过逆向工程来生成的,批量插入如下,是拼接成类似:insert into tb_enroll_student()values (),()…….();

这样的缺点是,数据库一般有一个默认的设置,就是每次sql操作的数据不能超过4M。这样插入,数据多的时候,数据库会报错Packet for query is too large (6071393 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.,虽然我们可以通过

类似 修改 my.ini 加上 max_allowed_packet =67108864,67108864=64M,默认大小4194304 也就是4M

修改完成之后要重启mysql服务,如果通过命令行修改就不用重启mysql服务。

完成本次操作,但是我们不能保证项目单次最大的大小是多少,这样是有弊端的。所以可以考虑进行分组导入。

三、分组把list导入Mysql中

同样适用mybatis批量插入,区别是对每次的导入进行分组计算,然后分多次进行导入:

 @Transactional(rollbackFor = Exception.class)
    public int addFreshStudentsNew2(List list, String schoolNo) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        List studentEntityList = new LinkedList<>();
        List enrollStudentEntityList = new LinkedList<>();
        List allusersEntityList = new LinkedList<>();

        for (FreshStudentAndStudentModel freshStudentAndStudentModel : list) {

            EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
            StudentEntity studentEntity = new StudentEntity();
            BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
            BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
            String operator = TenancyContext.UserID.get();
            String studentId = BaseUuidUtils.base58Uuid();
            enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
            enrollStudentEntity.setStudentId(studentId);
            enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            enrollStudentEntity.setOperator(operator);
            studentEntity.setId(studentId);
            studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            studentEntity.setOperator(operator);
            studentEntityList.add(studentEntity);
            enrollStudentEntityList.add(enrollStudentEntity);

            AllusersEntity allusersEntity = new AllusersEntity();
            allusersEntity.setId(enrollStudentEntity.getId());
            allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
            allusersEntity.setUserName(enrollStudentEntity.getName());
            allusersEntity.setSchoolNo(schoolNo);
            allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
            allusersEntity.setPassword(enrollStudentEntity.getNemtCode());  //密码设置为考生号
            allusersEntityList.add(allusersEntity);
        }

        int c = 100;
        int b = enrollStudentEntityList.size() / c;
        int d = enrollStudentEntityList.size() % c;

        int enResult = 0;
        int stuResult = 0;
        boolean allResult = false;

        for (int e = c; e <= c * b; e = e + c) {
            enResult = enrollStudentDao.insertAll(enrollStudentEntityList.subList(e - c, e));
            stuResult = studentDao.insertAll(studentEntityList.subList(e - c, e));
            allResult = allusersFacade.insertUserList(allusersEntityList.subList(e - c, e));
        }
        if (d != 0) {
            enResult = enrollStudentDao.insertAll(enrollStudentEntityList.subList(c * b, enrollStudentEntityList.size()));
            stuResult = studentDao.insertAll(studentEntityList.subList(c * b, studentEntityList.size()));
            allResult = allusersFacade.insertUserList(allusersEntityList.subList(c * b, allusersEntityList.size()));
        }

        if (enResult > 0 && stuResult > 0 && allResult) {
            return 10;
        }
        return -10;
    }

代码说明:

这样操作,可以避免上面的错误,但是分多次插入,无形中就增加了操作实践,很容易超时。所以这种方法还是不值得提倡的。

再次改进,使用多线程分批导入。

四、多线程分批导入Mysql

依然使用mybatis的批量导入,不同的是,根据线程数目进行分组,然后再建立多线程池,进行导入。

  @Transactional(rollbackFor = Exception.class)
    public int addFreshStudentsNew(List list, String schoolNo) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        List studentEntityList = new LinkedList<>();
        List enrollStudentEntityList = new LinkedList<>();
        List allusersEntityList = new LinkedList<>();

        list.forEach(freshStudentAndStudentModel -> {
            EnrollStudentEntity enrollStudentEntity = new EnrollStudentEntity();
            StudentEntity studentEntity = new StudentEntity();
            BeanUtils.copyProperties(freshStudentAndStudentModel, studentEntity);
            BeanUtils.copyProperties(freshStudentAndStudentModel, enrollStudentEntity);
            String operator = TenancyContext.UserID.get();
            String studentId = BaseUuidUtils.base58Uuid();
            enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
            enrollStudentEntity.setStudentId(studentId);
            enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            enrollStudentEntity.setOperator(operator);
            studentEntity.setId(studentId);
            studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
            studentEntity.setOperator(operator);
            studentEntityList.add(studentEntity);
            enrollStudentEntityList.add(enrollStudentEntity);

            AllusersEntity allusersEntity = new AllusersEntity();
            allusersEntity.setId(enrollStudentEntity.getId());
            allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
            allusersEntity.setUserName(enrollStudentEntity.getName());
            allusersEntity.setSchoolNo(schoolNo);
            allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
            allusersEntity.setPassword(enrollStudentEntity.getNemtCode());  //密码设置为考生号
            allusersEntityList.add(allusersEntity);
        });


        int nThreads = 50;

        int size = enrollStudentEntityList.size();
        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
        List> futures = new ArrayList>(nThreads);

        for (int i = 0; i < nThreads; i++) {
            final List EnrollStudentEntityImputList = enrollStudentEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));
            final List studentEntityImportList = studentEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));
            final List allusersEntityImportList = allusersEntityList.subList(size / nThreads * i, size / nThreads * (i + 1));

           Callable task1 = () -> {
          studentSave.saveStudent(EnrollStudentEntityImputList,studentEntityImportList,allusersEntityImportList);
               return 1;
            };
          futures.add(executorService.submit(task1));
        }
        executorService.shutdown();
        if (!futures.isEmpty() && futures != null) {
            return 10;
        }
        return -10;
    }

代码说明:

上面是通过应用ExecutorService 建立了固定的线程数,然后根据线程数目进行分组,批量依次导入。一方面可以缓解数据库的压力,另一个面线程数目多了,一定程度会提高程序运行的时间。缺点就是要看服务器的配置,如果配置好的话就可以开多点线程,配置差的话就开小点.

相关推荐

十年之重修Redis原理(redis重试机制)

弱小和无知并不是生存的障碍,傲慢才是。--------面试者...

Redis 中ZSET数据类型命令使用及对应场景总结

1.zadd添加元素zaddkeyscoremember...

redis总结(redis常用)

RedisTemplate封装的工具类packagehk.com.easyview.common.helper;importcom.alibaba.fastjson.JSONObject;...

配置热更新系统(如何实现热更新)

整体设计概览┌────────────┐┌────────────────┐┌────────────┐│配置后台服务│--写入-->│Red...

java高级用法之:调用本地方法的利器JNA

简介JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做javanativeinterface。要想使用JNI,我们需要在JAVA代码中定义native方法,然后通过javah命令...

SpringBoot:如何优雅地进行响应数据封装、异常处理

背景越来越多的项目开始基于前后端分离的模式进行开发,这对后端接口的报文格式便有了一定的要求。通常,我们会采用JSON格式作为前后端交换数据格式,从而减少沟通成本等。...

Java中有了基本类型为什么还要有包装类型(封装类型)

Java中基本数据类型与包装类型有:...

java面向对象三大特性:封装、继承、多态——举例说明(转载)

概念封装:封装就是将客观的事物抽象成类,类中存在属于这个类的属性和方法。...

java 面向对象编程:封装、继承、多态

Java中的封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)是面向对象编程的三大基本概念。它们有助于提高代码的可重用性、可扩展性和可维护性。...

怎样解析java中的封装(怎样解析java中的封装文件)

1.解析java中的封装1.1以生活中的例子为例,打开电视机的时候你只需要按下开关键,电视机就会打开,我们通过这个操作我们可以去间接的对电视机里面的元器件进行亮屏和显示界面操作,具体怎么实现我们并不...

python 示例代码(python代码详解)

以下是35个python代码示例,涵盖了从基础到高级的各种应用场景。这些示例旨在帮助你学习和理解python编程的各个方面。1.Hello,World!#python...

python 进阶突破——内置模块(Standard Library)

Python提供了丰富的内置模块(StandardLibrary),无需安装即可直接使用。以下是一些常用的内置模块及其主要功能:1.文件与系统操作...

Python程序员如何调试和分析Python脚本程序?附代码实现

调试和分析Python脚本程序调试技术和分析技术在Python开发中发挥着重要作用。调试器可以设置条件断点,帮助程序员分析所有代码。而分析器可以运行程序,并提供运行时的详细信息,同时也能找出程序中的性...

python中,函数和方法异同点(python方法和函数的区别)

在Python中,函数(Function)...

Python入门基础命令详解(python基础入门教程)

以下是Python基本命令的详解指南,专为初学者设计,涵盖基础语法、常用操作和实用示例:Python基本命令详解:入门必备指南1.Python简介特点:简洁易读、跨平台、丰富的库支持...