上万条数据的Excel导入,存库历程

1.  需求:页面上传16万条数据的excel, 后台一些过滤操作后存表

   一开始我想,就用就从常用的 JavaPOI导出Excel有三种形式选一个: HSSFworkbook,XSSFworkbook,SXSSFworkbook

  但是 实践过程中发现,事实并不是想象的那么随意,实践结果是  这三个都不能使用

 HSSFworkbook ,XSSFworkbook : 太小,解析导入的excel会报 内存溢出

SXSSFworkbook: 不是用于解析导入,只适用于导出,也不符合条件

想了解这三种方式的朋友可以参考:参考文档:https://blog.csdn.net/YiQ2018/article/details/81458149

Workbook wb = WorkbookFactory.create(file.getInputStream());这种方式仍然是行不通,等待时间无尽头

各种百度:发现了 EXCEL 转 CSV 处理  

这种方式就是过程有点复杂,还要去实现接口,想了解的朋友可以参考:

参考文档:https://blog.csdn.net/wanglizheng825034277/article/details/88662502

最终:发现新大陆:流方式读取  Excel2007-Excel07SaxReader

使用方式也是相当的容易,下面上步骤:

1. 项目中添加依赖:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.3.0</version>
</dependency>

使用示例:我上传的是只有手机号的一列, 对手机号进行处理校验

第一个参数可以传 文件、流、string等,想怎么传就怎么传。第二个参数是sheet的序号,-1表示读取所有sheet,0表示第一个sheet,依此类推。第三个就是处理逻辑

Set<String> mobiles = new HashSet<>();
Excel07SaxReader reader = ExcelUtil.read07BySax(file, 0, new RowHandler() {
    @Override
    public void handle(int sheetIndex, int rowIndex, List<Object> rowlist) {
        String mobile = rowlist.get(0).toString().trim();
        int size = rowlist.size();
        if (rowIndex == 0) {
            if (!mobile.equals("手机号") || size != 1) {
                try {
                    throw new Exception("模板错误!请重新导入");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            if (mobile.length() != 11) {
                log.error("文件名:"+fileName + "第" + rowIndex + "行手机号有误!" + "消息Id:" + noticeId);
            } else {
                mobiles.add(rowlist.get(0).toString());
            }
        }

    }
});

 

参考链接;https://hutool.cn/docs/#/

存库问题: 16万数据 存入库。

我的项目用是mybatis plus, 不管用的是哪一种,虽然都封装了 批量导入,但是底层 插入是一条一条的存入数据库,这样一来,频繁的和数据库进行交互,导致性能很慢。

解决方案:尝试用 insert 。。。values(),(),().  这样一来,确实快了不少,需要注意的是, values 后面也不能无限的追加, 凡事过犹不及。 使用分批 插入,一万 一万的存储。同时开一个单线程进行处理保存。这里特别说明一下,不要想着开多线程就会更快一点,其实并不。开多线程会占用更多的内存,导致内存溢出。

如此还会有新的问题,就是  堆内存溢出的问题。

由于处理,存储均在一个service中进行,导致内存不能得到释放,出现内存溢出错误,因此,又出来一个新的解决方法,就是将处理逻辑和保存分开来,在controller层进行调用。这样就解决了内存溢出的问题。不足之处就是不能在保证在同一个事务中进行,但是这个可以忽略掉。。

Controller 层


// --->从S3桶 里面 获取上传的文件
   List<NoticeReceiverTemp> noticeReceiverTempList = noticeReceiverTempService.selectList(new EntityWrapper<NoticeReceiverTemp>().eq("uuid", uuid));
     for (NoticeReceiverTemp temp : noticeReceiverTempList) {
                   List<String> mobiles = noticeReceiverService.dealOneFile(temp.getUuid(), temp.getFileName(), temp.getFileKey(), tenantId, noticeMainDTO.getReceiveType(), user.getUser_id(), noticeId);
                    noticeReceiverService.oneFile(mobiles,noticeMainDTO.getReceiveType(),user.getUser_id(),temp.getFileName(),uuid, noticeId);
                }
Service 层


 @Override
    public List<String> dealOneFile(String uuid, String fileName, String key, String tenantId, Integer receiveType, Long userId, Long noticeId) throws Exception {
        long beginTime = System.currentTimeMillis();
        Set<String> mobiles = new HashSet<>();
        log.info("消息中心导入会员开始时间:" + beginTime);
        InputStream in = s3Util.amazonS3Downloading(s3Util.getS3Client(), CrmConstants.AWS_BUCKET_NAME_CRM + "/msg", key, "");
        Excel07SaxReader reader = ExcelUtil.read07BySax(in, 0, new RowHandler() {
            @Override
            public void handle(int sheetIndex, int rowIndex, List<Object> rowlist) {
                String mobile = rowlist.get(0).toString().trim();
                int size = rowlist.size();
                if (rowIndex == 0) {
                    if (!mobile.equals("手机号") || size != 1) {
                        try {
                            throw new Exception("模板错误!请重新导入");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    if (mobile.length() != 11) {
                        log.error("文件名:"+fileName + "第" + rowIndex + "行手机号有误!" + "消息Id:" + noticeId);
                    } else {
                        mobiles.add(rowlist.get(0).toString());
                    }
                }

            }
        });

        if (mobiles.size() < 1) {
            throw new Exception("文件内容不能为空,请重新上传!");
        }
        List<String> uploadMobiles = new ArrayList<>(mobiles);
        return uploadMobiles;
    }

    @Override
    public void oneFile(List<String> uploadMobiles,Integer receiveType,Long userId,String fileName,String uuid,Long noticeId) throws Exception {
        ExecutorService executorService= Executors.newSingleThreadExecutor();
        executorService.submit(()-> {
            int batchSize = 10000;
            if (uploadMobiles.size() > batchSize) {
                int size = 0;
                if (uploadMobiles.size() % batchSize == 0) {
                    size = uploadMobiles.size() / batchSize;
                } else {
                    size = uploadMobiles.size() / batchSize + 1;
                }
                for (int i = 0; i < size; i++) {
                    if (i != size - 1) {
                            // 一次处理一万条,同时存表
                            try {
                                addReceiversDeal(receiveType, noticeId, userId, uploadMobiles.subList(i * batchSize, batchSize * (i + 1)), fileName, uuid);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                    } else {
                            // 一次处理一万条,同时存表
                            try {
                                addReceiversDeal(receiveType, noticeId, userId, uploadMobiles.subList(i * batchSize, uploadMobiles.size()), fileName, uuid);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                    }
                }
            } else {
                try {
                    addReceiversDeal(receiveType, noticeId, userId, uploadMobiles, fileName, uuid);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }

    /**
     * 分批存一万
     *
     * @param receiveType
     * @param noticeId
     * @param userId      187@param noticeReceiverMobiles
     * @param fileName
     * @param uuid
     */
    private void addReceiversDeal(Integer receiveType, Long noticeId, Long userId, List<String> noticeReceiverMobiles, String fileName, String uuid) throws Exception {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<NoticeReceiver> receivers = new ArrayList<>(noticeReceiverMobiles.size());
        for (String mobile : noticeReceiverMobiles) {
            receivers.add(new NoticeReceiver(noticeId, receiveType, mobile, 0, userId, format.format(new Date()), fileName, uuid));
        }
        if (!CollectionUtils.isEmpty(receivers)) {
            noticeReceiverMapper.insertBatchByMySelf(receivers);
            receivers = null;
        }
    }

 

内存使用对比图:单线程保存

多线程保存(开2个线程 就会报堆内存溢出错误)

到此结束!希望帮到你

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值