easyExcel百万级别数据入库、导入、写文件操作

最近公司有个大批量excel数据入库的需求,原封装的poi工具老是内存溢出,现考虑使用alibaba的easyExcel,废话不多说,直接上代码 ,可以按照步骤来,根据自己的业务需求修改即可,百万数据的话,用了时间在一两分钟之内。学习时间不长,各位大佬看哪些不合理的地方,提出指导意见,不胜感激!

步骤一:
	添加依赖等操作就忽略了
	创建实体类,加入ExcelProperty注解,对应excel列名
@Data
public class MissWireOriDataExcelInfo {

    @ExcelProperty(index=0,value = "电能表资产编号")
    private String meterNo;
    
    @ExcelProperty(index=1,value ="A相电压")
    private double uA;

    @ExcelProperty(index=2,value ="B相电压")
    private double uB;

    @ExcelProperty(index=3,value ="C相电压")
    private double uC;

    @ExcelProperty(index=4,value ="A相电流")
    private double iA;

    @ExcelProperty(index=5,value ="B相电流")
    private double iB;

    @ExcelProperty(index=6,value ="C相电流")
    private double iC;

    @ExcelProperty(index=7,value ="A相有功功率")
    private double pA;

    @ExcelProperty(index=8,value ="B相有功功率")
    private double pB;

    @ExcelProperty(index=9,value ="C相有功功率")
    private double pC;

    @ExcelProperty(index=10,value ="有功功率总")
    private double pTotal;

    @ExcelProperty(index=11,value ="A相无功功率")
    private double qA;

    @ExcelProperty(index=12,value ="B相无功功率")
    private double qB;

    @ExcelProperty(index=13,value ="C相无功功率")
    private double qC;

    @ExcelProperty(index=14,value ="无功功率总")
    private double qTOTAL;

    @ExcelProperty(index=15,value ="数据时间")
    private String dataTime;
}
步骤二:
	创建对应的service、dao、controller此处省略
步骤三(核心):
	创建对应的监听器
	
/**
 * @Author: huang
 * @Description: 批量excel数据导入监听器
 * @DateTime: 2024/7/17 17:49
 **/
@Slf4j
public class EasyExcelImportHandler implements ReadListener<MissWireOriDataExcelInfo> {
    //线程处理
    private static final ThreadPoolExecutor threadPoolExecutor;
    static {
        // 系统可用处理器的虚拟机数量
        int processors = Runtime.getRuntime().availableProcessors();
        threadPoolExecutor = new ThreadPoolExecutor(
                processors + 1,              // 核心线程数
                processors * 2 + 1,          // 最大线程数
                10 * 60,                     // 线程空闲时间
                TimeUnit.SECONDS,            // 时间单位
                new LinkedBlockingQueue<>(1000000) // 工作队列
        );
    }

    /*成功数据*/
    private Date now = new Date();
    private String batchId = DateUtil.dateToString(now, "yyyyMMddHHmmss");
    private List<MissWireOriDataExcelInfo> successList = new ArrayList<>();
    //    /*单次处理条数*/
    private final static int BATCH_COUNT = 100000;

    //必须通过构造函数注入
    private MissWireOridataMapper missWireOridataMapper;

    private MissWireBatchRecordMapper missWireBatchRecordMapper;

    private MissWireOridataService missWireOridataService;

    public EasyExcelImportHandler(MissWireOridataMapper missWireOridataMapper) {
        this.missWireOridataMapper = missWireOridataMapper;
    }

    public EasyExcelImportHandler(MissWireOridataMapper missWireOridataMapper, MissWireBatchRecordMapper missWireBatchRecordMapper, MissWireOridataService missWireOridataService) {
        this.missWireOridataMapper = missWireOridataMapper;
        this.missWireBatchRecordMapper = missWireBatchRecordMapper;
        this.missWireOridataService = missWireOridataService;
    }

    @Value("${misswire.excel.basepath}")
    private String basePath;

    private static StringBuilder stringBuilder = new StringBuilder();
    private String fileName;
    private MultipartFile file;

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public void setFile(MultipartFile file) {
        this.file = file;
    }


//    /**
//     * @Description: 转换异常,获取其他异常下会调用本接口,抛出异常则停止读取,如果不抛出异常,继续读取下一行
//     * @DateTime: 17:56 2024/7/17
//     * @Params: [e, analysisContext]
//     * @Return void
//     */
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
        throw e;
    }


    
//    /**
//     * @Author: 
//     * @Description: 读取表格内容,解析表格每条数据
//     * @DateTime: 17:53 2024/7/17
//     * @Params: [user, analysisContext]
//     * @Return void
//     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invoke(MissWireOriDataExcelInfo oriData, AnalysisContext analysisContext) {
//        log.info("解析到一条数据:{}", oriData);
        stringBuilder.setLength(0);
        //数据校验
        validateData(oriData);

        // 如果有错误信息,抛出异常
        if (stringBuilder.length() > 0) {
            log.warn("数据校验失败: {}", stringBuilder.toString());
            throw new ValidationException(stringBuilder.toString());
        }

        successList.add(oriData);
        if (successList.size() >= BATCH_COUNT) {
//            log.info("读取数据:{}", successList.size());
            saveData();
        }
    }

    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
    }

    private void validateData(MissWireOriDataExcelInfo data) {
        String numberPattern = "^0|null|\\d+(\\.\\d+)?$";
        String dateTimePattern = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$";
        //进行数据判断
        if (StringUtils.isEmpty(data.getMeterNo())) {
            stringBuilder.append("电能表不能为空");
        }
        if (data.getDataTime() == null || !data.getDataTime().toString().matches(dateTimePattern)) {
            stringBuilder.append("数据时间 (Data_TIME) 不能为空,且时间要为yyyy-MM-dd HH:mm:ss格式!<br/>");
        }
    }

    //
    private boolean isValidNumber(double value, String pattern) {
        return String.valueOf(value).matches(pattern);
    }

    //
//    /**
//     * 采用多线程读取数据
//     */
    @Transactional(rollbackFor = Exception.class)
    void saveData() {
        log.info("开始进入批量入库保存");
        List<List<MissWireOriDataExcelInfo>> lists = ListUtil.split(successList, 20000);
        CountDownLatch countDownLatch = new CountDownLatch(lists.size());
        for (List<MissWireOriDataExcelInfo> list : lists) {
            threadPoolExecutor.execute(() -> {
                try {
                    List<MissWireOridata> collect = list.stream().map(o -> {
                        int segmentId = DateUtil.GetSegIdByDate(DateUtil.stringToDate(o.getDataTime()), 15);
                        MissWireOridata ori = new MissWireOridata();
                        ori.setSegmentId(segmentId);
                        ori.setBatchId(batchId);
                        ori.setMeterNo(o.getMeterNo());
                        ori.setDataTime(DateUtil.stringToDate(o.getDataTime()));
                        ori.setUa((float) o.getUA());
                        ori.setUb((float) o.getUB());
                        ori.setUc((float) o.getUC());
                        ori.setIa((float) o.getIA());
                        ori.setIb((float) o.getIB());
                        ori.setIc((float) o.getIC());
                        ori.setPTotal((float) o.getPTotal());
                        ori.setPa((float) o.getPA());
                        ori.setPb((float) o.getPB());
                        ori.setPc((float) o.getPC());
                        ori.setQa((float) o.getQA());
                        ori.setQb((float) o.getQB());
                        ori.setQc((float) o.getQC());
                        ori.setQTotal((float) o.getQTOTAL());
                        return ori;
                    }).collect(Collectors.toList());
                    missWireOridataService.saveBatch(collect);
                } catch (Exception e) {
                    log.error("启动线程失败,e:{}", e.getMessage(), e);
                } finally {
                    //执行完一个线程减1,直到执行完
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有线程执行完
        try {
            countDownLatch.await();
        } catch (Exception e) {
            log.error("等待所有线程执行完异常,e:{}", e.getMessage(), e);
        }
        // 提前将不再使用的集合清空,释放资源
        successList.clear();
        lists.clear();
    }

    public List<MissWireOridata> getOriData(List<MissWireOriDataExcelInfo> excelInfoList) {
        List<MissWireOridata> oriDataList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(excelInfoList)) {
            for (MissWireOriDataExcelInfo wireOriDataExcelInfo : excelInfoList) {
                MissWireOridata m = new MissWireOridata();
                BeanUtil.copyProperties(wireOriDataExcelInfo, m);
                oriDataList.add(m);
            }
        }
        return oriDataList;
    }
    //
//    /**
//     * 所有数据读取完成之后调用
//     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        String s = StrUtil.subBefore(fileName, '.', false);
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        String filePathName = s + "_" + batchId + suffix;
        //读取剩余数据
        if (CollectionUtils.isNotEmpty(successList)) {
//            log.info("读取数据:{}条", successList.size());
            List<MissWireOridata> oriData = getOriData(successList);
            if (CollectionUtil.isNotEmpty(oriData)) {
                missWireOridataService.saveBatch(oriData);
            }
            //定义批次数据入库
            MissWireBatchRecord batchRecord = new MissWireBatchRecord();
            batchRecord.setId(batchId);
            batchRecord.setDataSource("导入");
            //源数据名称 源文件名称+批次号
            batchRecord.setSourceData(s);
            batchRecord.setAnalysisState(CommonConstant.MISS_WIRE_ANALYSIS_STATE);
            batchRecord.setImportType(CommonConstant.IMPORT_COLLECT_TYPE);
            batchRecord.setRecordDate(DateUtil.dateToString(now, "yyMMdd"));
            batchRecord.setInsertTime(new Date());
            batchRecord.setUpdateTime(new Date());
            //批量保存
            missWireBatchRecordMapper.insert(batchRecord);
        }
        try {
            saveFile(file, "D:/uploadColData", filePathName);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private void saveFile(MultipartFile file, String directory, String fileName) throws IOException {
        File dir = new File(directory);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new IOException("无法创建目录: " + directory);
            }
        }

        File destFile = new File(dir, fileName);
        try (InputStream in = file.getInputStream();
             OutputStream out = new FileOutputStream(destFile)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            log.info("保存数据文件成功,路径{}", destFile);
        } catch (IOException e) {
            log.error("文件保存失败: {}", e.getMessage(), e);
            throw e; // 重新抛出异常以便上层调用者处理
        }
    }


步骤四:
    @PostMapping("/importOriData")
    @QueryTransferAnnotation
    public ResultVo importOriData(MissWireBatchRecordQo qo) throws IOException {
        try {
            log.info("采集数据导入开始:");
            long start = System.currentTimeMillis();
            EasyExcelImportHandler handler=new EasyExcelImportHandler(missWireOridataMapper,missWireBatchRecordMapper,missWireOridataService);
            String originalFilename = qo.getFile().getOriginalFilename();
//            // 将文件名传递给 EasyExcelImportHandler
            handler.setFileName(originalFilename);
            handler.setFile(qo.getFile());
//            // 将文件名传递给 EasyExcelImportHandler
            // 使用 EasyExcel 读取文件并处理
            EasyExcel.read(qo.getFile().getInputStream(),
                    MissWireOriDataExcelInfo.class, handler).sheet().doRead();
            long end = System.currentTimeMillis();
            log.info("采集数据导入结束,消耗时间:{}",end-start);
            return ResultVo.build().success("数据导入成功");
        } catch (ValidationException e) {
            return ResultVo.build().failed(e.getMessage());
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值