报表导出很简单,你考虑过如何优化吗?

廉价感情. 提交于 2020-12-18 07:01:57

点击上方“小罗技术笔记”,关注公众号

第一时间送达实用干货


本文来自我个人实际开发的总结,可能有不合理的地方,欢迎大家批评指教!

在本文开始前先回答上一篇文章(你知道一棵B+树可以存放多少行数据吗?)遇到的一个提问。

答案:并不一定,取决于数据量一个高度为 3 的 B+ 树大概可以存放 1170 × 1170 × 16 = 21902400 行数据,已经是千万级别的数据量了。(都到这个数据量了该考虑分库分表啦!若有更好答案可以留言回复)

报表导出干开发的基本上都做过,大部分基本上遵循查询数据 → 写入数据到excel返回到前端这个步骤。没错我刚开始工作的时候也是这样干的!

以下这段代码大家肯定很熟悉,常见的报表导出就是这样的。

     
   
   
  1. //给文件命名。随机命名

  2. String fileName = "Excel-" + String.valueOf(System.currentTimeMillis()).substring(4, 13) + ".xls";

  3. //告诉浏览器数据格式,将头和数据传到前台

  4. String headStr = "attachment; filename=\"" + fileName + "\"";

  5. response.setContentType("APPLICATION/OCTET-STREAM");

  6. response.setHeader("Content-Disposition", headStr);

  7. OutputStream out = response.getOutputStream();

  8. //查询数据

  9. List<Object> dataList=new ArrysList();

  10. //调用poi的工具类

  11. ExportExcel ex = new ExportExcel(title, rowsName, dataList);

  12. try {

  13.    ex.export(out);

  14. } catch (Exception e) {

  15.    e.printStackTrace();

  16. }finally{

  17. out.flush();

      out.close();

  1. }

说句心里话,再简单的功能使用的人数多起来了都会有问题出现的,下面是我实际工作中总结的一点经验。

一、分批写入

随着系统的运行数据量会越来越大报表导出的数据量也会相应的变大,平时我们的操作就像上面的代码一样一次查出所有的数据写入excel,这样做在数据量很大时会导致内存溢出异常(list中的数据是存在虚拟机运行内存中的!)。这时我们就要把数据拆分,分批查出分批写入。如下例:

     
   
   
  1. //获取d://test.xls,建立数据的输入通道

  2. FileInputStream fileInputStream=new FileInputStream("d://test.xls");

  3. POIFSFileSystem poifsFileSystem=new POIFSFileSystem(fileInputStream);

  4. //使用POI提供的方法得到excel的信息

  5. HSSFWorkbook Workbook=new HSSFWorkbook(poifsFileSystem);

  6. //根据name获取sheet表

  7. HSSFSheet sheet=Workbook.getSheet("sheet2");

  8. //获取第一行

  9. HSSFRow row=sheet.getRow(0);

  10. //向d://test.xls中写数据

  11. FileOutputStream out=new FileOutputStream("d://test.xls");

  12. //对总行数减4,就是倒数行数加数据

  13. row=sheet.createRow((short)(sheet.getLastRowNum()-4));

  14. row.createCell(0).setCellValue("11111");

  15. //设置第一个(从0开始)单元格的数据

  16. row.createCell(5).setCellValue(11111);

  17. out.flush();

  18. Workbook.write(out);

  19. out.close();

二、异步处理

平时我们导出报表都是一点导出按钮浏览器就与后端建立数据连接等待后端把数据处理完了返回给前端,但是如果遇到很大数据量的导出时会花费很久时间,用户页面上就一直出现下载中体验感不好。(我就是因为这个导出时间过长被用户投诉过,代码已经尽量优化了,奈何数据量太大了) 对于这种我们最好是导出和下载分开,用户点击导出我们后台运行生成excel上传到文件服务器,在前端页面给一个下载页面,用户自行从文件服务器下载数据。

三、导出排队

大部分系统对导出功能没有加任何限制,用户一点击就马上执行。可以想一下一个系统同时几百人去点击导出会咋样。记得项目上线半年后开始逐渐出现用户点击导出系统卡死的情况,后台一排查一堆导出报表的进程消耗了系统很多资源(也有可能导致虚拟机内存不够)。没得办法我们将导出做了一个排队限制系统同时执行导出任务的数量,将任务分摊到各个时间段来执行。

四、建立数据基准点

之前是做财务系统经常要导出各个项目的预算资金执行情况,每次导出都是全量查询该项目的数据然后做计算,导致报表导出都要等很久,有时用户急着需要却半天出不来!对此我们对每个项目的财务报表数据做了个基础数据,每天凌晨查询前一天该项目的报表数据变更情况结合上一次的基准数据做更新,当天查询时只需要查询项目当天的实时运行数据,在基准数据上加减。这样避免了每次查询全量计算项目的所有数据。

通过以上优化系统再也没有出现报表导出卡顿的情况,也获得了用户的好评!

分配写入数据的demo我就不提供了,网上大把的例子(面向互联网编程)。

近期好文:

1、你知道一棵B+树可以存放多少行数据吗?

2、尖峰高流量并发,12306是采用那种解决方案


长按二维码关注

点个在看再走呗!


本文分享自微信公众号 - 小罗技术笔记(javaCodeNote)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!