百万级别账单excel导出

1. V1-实现思路

将所有数据一次性查出来,再写入一个页面(sheet)。考虑到查询效率和内存,一次性查出百万条数据不现实。

2003(.xls)文件:最大256(IV,2的8次方)列,最大65536(2的16次方)行;即横向256个单元格,竖向65536个单元格。(.xlsx)文件:最大16384(XFD,2的14次方)列,最大1048576(2的20次方)行;即横向16384个单元格,竖向1048576个单元格。

考虑内存溢出和查询效率。将数据分页条数设置65536,再导出Excel,具体看自身分页查询效率。

2. 引入依赖easyexcel

        
        
            com.alibaba
            easyexcel
            2.2.6
        

3. 核心代码

    @Override
    public String queryBillExportV1() {
        //TODO 1.查到账单总数
        Integer total = payBillDao.countPayBill();
        //TODO 2.根据sheetTotal条数分页查询 > 导出
        int count = (total % sheetTotal == 0) ? total / sheetTotal : total / sheetTotal + 1;
//        int count = 5;
        
        String fileName = "D:\\360Downloads\\learn\\demo\\baoxian\\logs\\账单1.xlsx";

        ExcelWriter excelWriter = null;
        try {
            // 这里指定文件
            excelWriter = EasyExcel.write(fileName, PayBill.class).build();
            // 实际使用时根据数据库分页的总的页数来。
            for (int i = 1; i <= count; i++) {
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                Page page = PageHelper.startPage(i, sheetTotal);
                Map map = new HashMap<>(2);
                map.put("offset", String.valueOf(page.getStartRow()));
                map.put("pageSize", String.valueOf(sheetTotal));
                PageHelper.clearPage();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List data = payBillDao.queryBillExportV1(map);
                logger.info("开始导出sheet{}", i);
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                excelWriter.write(data, writeSheet);
                logger.info("结束导出sheet{}", i);
            }
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
        return fileName;
    }
 
 
分页平均时间1-2分

写磁盘平均8s

40w数据 文件大小

posman显示请求时间16分钟

从导出结果来看40多万数据需要耗时16分钟,需要优化的地方,主要是查询效率,数据持久化到磁盘10s以内是可以接受的

4.优化分析

数据导出流程

从上图可以看出,可以优化的有两个方面
1.获取数据,利用线程池多线程查询数据
2.持久化数据,多线程写不同sheet

5.V2账单导出

  • 定义线程池
    private static final BlockingQueue BLOCKING_QUEUE = new ArrayBlockingQueue(100);
    private static final ThreadPoolExecutor.CallerRunsPolicy POLICY = new ThreadPoolExecutor.CallerRunsPolicy();
    private static int corePoolSize= Runtime.getRuntime().availableProcessors()<2?2:Runtime.getRuntime().availableProcessors();
    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(corePoolSize, corePoolSize*4, 60, TimeUnit.SECONDS, BLOCKING_QUEUE, POLICY);
  • 定义查询分页数据的callable
    /**
     * 定义多线程Callable
     */
    class PoiPayBillCallable implements Callable> {
        private Integer pageNo;
        private Integer pageSize;

        public PoiPayBillCallable(Integer pageNo, Integer pageSize) {
            this.pageNo = pageNo;
            this.pageSize = pageSize;
        }

        @Override
        public List call() throws Exception {
            logger.info("----当前线程:{},查询分页{}", Thread.currentThread().getName(), pageNo);
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            Page page = PageHelper.startPage(pageNo, pageSize);
            Map map = new HashMap<>(2);
            map.put("offset", String.valueOf(page.getStartRow()));
            map.put("pageSize", String.valueOf(pageSize));
            PageHelper.clearPage();
            logger.info("----offset={},pageSize={}", map.get("offset"), map.get("pageSize"));
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            List data = payBillDao.queryBillExportV2(map);
            return data;
        }
    }
 
 
  • v2核心代码
    /**
     * TODO 1.查到账单总数
     * TODO 2.根据sheetTotal条数分页查询-多线程查询合并条数 > 导出
     *
     * @return
     */
    @Override
    public String queryBillExportV2() {
        String fileName = "D:\\360Downloads\\learn\\demo\\baoxian\\logs\\账单2.xlsx";
        //TODO 1.查到账单总数
        Integer total = payBillDao.countPayBill();
        //TODO 2.根据sheetTotal条数分页查询 > 导出
        int pageSize = 13107;
        //根据sheet分区间
        int count = (total % sheetTotal == 0) ? total / sheetTotal : total / sheetTotal + 1;
        //分页,sheet和分页数要整除.多线程分小页查询避免oom,直接用sheetTotal做分页导出时间5分
        int page = (sheetTotal % pageSize == 0) ? sheetTotal / pageSize : sheetTotal / pageSize + 1;
        int pageall = (total % pageSize == 0) ? total / pageSize : total / pageSize + 1;

        //创建Excel对象
        ExcelWriter excelWriter = EasyExcel.write(fileName, PayBill.class).build();
        try {
            for (int i = 1; i <= count; i++) {
                //并发编程--定义callable
                List calls = new ArrayList<>();
                for (int j = (i - 1) * page == 0 ? 1 : (i - 1) * page; j < i * page; j++) {
                    if (j > pageall) {
                        continue;
                    }
                    calls.add(new PoiPayBillCallable(j, pageSize));
                }
                if (i == count && i * page <= pageall) {
                    calls.add(new PoiPayBillCallable(pageall, pageSize));
                }

                List>> list = EXECUTOR.invokeAll(calls);
                logger.info("V2结束导出sheet{}", i);
                for (Future> future : list) {
                    final List payBills = future.get();
                    //持久化数据
                    WriteSheet writerSheet = EasyExcel.writerSheet(1, "账单" + 1).build();
                    excelWriter.write(payBills, writerSheet);
                }
                logger.info("V2结束导出sheet{}", i);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            logger.info("关闭Excel流");
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
        return fileName;
    }
  • 导出耗时,效率差不多提高了3倍


    耗时6分

你可能感兴趣的:(百万级别账单excel导出)