昨天公司的数据那边的人导出操作日志,一次性导出30w+让服务直接挂掉了,所有有了这篇文章...
正常从数据库导出少量数据到execl,直接将符合条件的数据全部查询放到一个List中然后写到execl里即可,但是数据量过大时会导致内存兜不住的情况.
解决思路就是:一次性查询1w条数据加载到内存中,然后将这1w条数据写到execl里,再使用分页查询下一批1w的数据...实现如下:
@PostMapping("/operationLog") public void operationLog(HttpServletResponse response, @RequestBody Dashboard dashboard) throws IOException { String fileName = URLEncoder.encode("操作日志", "UTF-8").replaceAll("\\+", "%20");//设置文件名 String ymds = DateUtil.format(new Date(), "yyyyMMddHHmmss"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ymds + ".xlsx");//设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //设置响应内容类型 response.setCharacterEncoding("utf-8");//编码 ExcelWriter writer = EasyExcel.write(response.getOutputStream(), OparationExcel.class).build(); WriteSheet sheet = EasyExcel.writerSheet("操作日志").build(); Page<OparationExcel> page = dashboardFeign.operationLogPage(1L, 10000L, dashboard); long total = page.getTotal(); if (total <= 10000) { writer.write(page.getRecords(), sheet); writer.finish();//关闭流 return; } // excelWriter.write(page.getRecords(), writeSheet); int ceil = (int) Math.ceil(NumberUtil.div(total, 10000L)); for (int i = 1; i <= ceil; i++) { page.setCurrent(i); page = dashboardFeign.operationLogPage((long) i, 10000L, dashboard); writer.write(page.getRecords(), sheet); } writer.finish();//关闭流 }
注意!!!:我这里使用的分页是mybatisplus提供的分页功能,有一个小小的问题需要注意的,mybatisplus的分页参数size,最大值为500,如果需要打破这个限制,需要自己写一个
MybatisPlusConfig继承MetaObjectHandler接口并重写paginationInterceptor方法,实现如下:
@Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置最大单页限制数量,默认 500 条,-1 不受限制 paginationInterceptor.setLimit(-1); return paginationInterceptor; }
另外一个思路就是自己用查询页码(current)和每页数量(size)计算limit的两个值,手写SQL,都是很简单的实现.
另外一点,我这是是将所有数据都写到一个工作簿(sheet)当中,如果你希望分开写,只需要将sheet在循环中创建即可,EasyExcel.writerSheet()可传两个参数,第一个是索引(Integer),第二个是名称(String),
这也是基本实现需要,能导出大量数据,服务也不会挂,还有很多可以优化的地方,比如一次性写两万或者更多,具体看你们的服务器咋样,我们没有做这个测试所以选择牺牲效率,还有一点优化的地方(想到了但是我没做),这个逻辑是读出来然后写进execl,其实在写的时候我们就可以去查询下一次的数据加载到内存或者Redis中,而不需要写完之后再去查.
刚毕业还在实习,有问题希望大佬指正,感谢!!!