大数据量导出EXCEL-导出50个字段

数据库数据:226万   
导出10万条数据,每条数据50个字段:导出时间13秒左右

一,所用技术

    EasyExcel, 线程池 ThreadPoolExecutor ,原子类 AtomicInteger ,阻塞队列, Mysql 索引

二,设计方案

2.1 EasyExcel 是一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成 Excel 的读、写等功能。
2.2 Mysql 查询优化
2.3 ,并发查询设计
2.4 ,读写分离设计(数据读取和数据写入 EXCEL 并发进行)

三,详细说明

3.1Mysql 查询优化

1.1 需要时间查询增加时间的索引

1.2 数据量大需要分页查询,分页查询页数越大会查询越慢
分页查询时,可以通过子查询单独查询出主键 ID 字段,由于主键 ID 加了索引,查询速度会
非常快,再通过子查询继续查询出其他需要的字段
SELECT ..........
FROM digital_case_info c
WHERE id>=(
SELECT id
FROM digital_case_info
WHERE 1=1 AND happen_date >='2019-09-14 00:00:00' AND happen_date <='2019-10-10
00:00:00' order by happen_date asc LIMIT 80000,1
)
LIMIT 20000;

耗时:

索引使用情况:

3.2,并发查询设计

3.3,读写分离设计

通过定义一个阻塞队列,在主线程中新起多个线程用于查询 ([ 并发 ] 查询时将查询结果放入队列
) ,写入时直接从队列中进行获取待写入数据,实现读写分离和降低写入时等待查询数据的时间, 从而降低总体用时。

 四,最终源码

@Autowired
    private DigitalCaseInfoMapper digitalCaseInfoMapper;


    @Resource
    private ThreadPoolExecutor executor;

    /**
     * 每个线程查询的页数
     */
    private static final int PAGE_NUM = 20000;

    /**
     * 阻塞队列获取数据超时时间
     */
    private static final Integer PAGE_SEARCH_TIMEOUT_SECONDS = 60;



    @Override
    public void export(HttpServletResponse response) {
        log.info("sql分页导出excel....");
        long start=System.currentTimeMillis();

        //定义easyExcel导出的对象
        ExcelWriter excelWriter = null;
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");
        WriteSheet writeSheet = EasyExcel.writerSheet("sheet1").build();
        try{
            //查询条件
            Map<String, Object> map=new HashMap<>();
            map.put("startTime","2019-09-14 00:00:00");
            map.put("endTime","2019-10-10 00:00:00");
            //获取总数
            long count = digitalCaseInfoMapper.countAll(map);
            //计算开启的线程数
            int loopNum = new Double(Math.ceil((double)count / PAGE_NUM )).intValue();
            log.info("多线程查询,总数:{},开启线程数:{}",count,loopNum);
            //队列存放数据库中查询导的数据
            ArrayBlockingQueue<List<DigitalCaseInfo>> queue = new ArrayBlockingQueue<>(loopNum, true);
            //当数据为空时跳出写入循环
            AtomicInteger atomicInteger=new AtomicInteger(loopNum);
            //并发查询数据
            executeTask(queue,loopNum,count);
            List<DigitalCaseInfo> list=null;
            excelWriter = EasyExcel.write(response.getOutputStream(),DigitalCaseInfo.class).build();
            //读写分离式写入
            while ((list = queue.poll(PAGE_SEARCH_TIMEOUT_SECONDS, TimeUnit.SECONDS)) != null) {
                excelWriter.write(list,writeSheet);
                //跳出循环
                if(0==atomicInteger.decrementAndGet()){
                    break;
                }
            }
            long end=System.currentTimeMillis();
            log.info("导出耗时:" + (end-start));
        }catch (Exception e){
            log.debug("文件导出报错,{}",e.getMessage());
        }finally {
            if(excelWriter != null ){
                excelWriter.finish();
            }
        }


    }

    /**
     * 并发查询数据库
     * @param queue 存放数据队列
     * @param loopNum 查询次数
     * @param count 总数
     */
    private void executeTask(ArrayBlockingQueue<List<DigitalCaseInfo>> queue, int loopNum, long count) {
        //loopNum并发查询次数
        for (int i = 0; i < loopNum; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("offset", i * PAGE_NUM);
            if( i == loopNum -1 ){
                map.put("pageSize",count - PAGE_NUM * i);
            }else{
                map.put("pageSize",PAGE_NUM);
            }
            map.put("startTime","2019-09-14 00:00:00");
            map.put("endTime","2019-10-10 00:00:00");
            //并发查询后放入队列中
            executor.execute(()->{
                long s=System.currentTimeMillis();
                List<DigitalCaseInfo> caseInfos = digitalCaseInfoMapper.findList(map);
                try {
                    queue.put(caseInfos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            log.info("开始查询第{}条开始的{}条记录",i * PAGE_NUM, PAGE_NUM);
        }

    }

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
如果要使用 easypoi 导出多个 sheet 页并且处理大数据,可以采用以下步骤: 1. 创建一个 Workbook 对象,并设置每个 sheet 页的数据源和样式。可以使用 easypoi 提供的工具类来创建和设置样式。 2. 将每个 sheet 页添加到 Workbook 中。 3. 如果数据较大,可以采用分页查询的方式,每次查询一部分数据,然后将数据添加到对应的 sheet 页中。 4. 在导出时设置文件名和响应头,将 Workbook 输出到响应流中。 下面是一个示例代码,可以参考一下: ``` @RequestMapping("/export") public void export(HttpServletResponse response) { Workbook workbook = new SXSSFWorkbook(); // 导出第一个 sheet Sheet sheet1 = workbook.createSheet("Sheet1"); List<Data1> data1List = getData1List(); setSheet1Data(sheet1, data1List); // 导出第二个 sheet Sheet sheet2 = workbook.createSheet("Sheet2"); List<Data2> data2List = getData2List(); setSheet2Data(sheet2, data2List); // 设置响应头 response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=test.xlsx"); // 输出到响应流中 try { workbook.write(response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } private void setSheet1Data(Sheet sheet, List<Data1> data1List) { // 设置表头样式 CellStyle headerStyle = ExcelStyleUtil.getHeaderStyle(sheet.getWorkbook()); // 设置表格数据样式 CellStyle dataStyle = ExcelStyleUtil.getDataStyle(sheet.getWorkbook()); // 设置表头 Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("字段1"); headerRow.createCell(1).setCellValue("字段2"); headerRow.createCell(2).setCellValue("字段3"); for (Cell cell : headerRow) { cell.setCellStyle(headerStyle); } // 添加数据 int rowIndex = 1; for (Data1 data1 : data1List) { Row row = sheet.createRow(rowIndex++); row.createCell(0).setCellValue(data1.getField1()); row.createCell(1).setCellValue(data1.getField2()); row.createCell(2).setCellValue(data1.getField3()); for (Cell cell : row) { cell.setCellStyle(dataStyle); } } } private void setSheet2Data(Sheet sheet, List<Data2> data2List) { // 设置表头样式 CellStyle headerStyle = ExcelStyleUtil.getHeaderStyle(sheet.getWorkbook()); // 设置表格数据样式 CellStyle dataStyle = ExcelStyleUtil.getDataStyle(sheet.getWorkbook()); // 设置表头 Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("字段1"); headerRow.createCell(1).setCellValue("字段2"); headerRow.createCell(2).setCellValue("字段3"); for (Cell cell : headerRow) { cell.setCellStyle(headerStyle); } // 添加数据 int rowIndex = 1; for (Data2 data2 : data2List) { Row row = sheet.createRow(rowIndex++); row.createCell(0).setCellValue(data2.getField1()); row.createCell(1).setCellValue(data2.getField2()); row.createCell(2).setCellValue(data2.getField3()); for (Cell cell : row) { cell.setCellStyle(dataStyle); } } } ``` 其中,`ExcelStyleUtil` 是一个工具类,用来设置表格样式,可以根据需要自行实现。`SXSSFWorkbook` 是一个针对大数据的 Workbook 实现,可以避免内存溢出的问题。在实际应用中,可以根据具体需求进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值