☰
【springboot】excel 大数据量导出:分页查询 + 分 sheet 设置数据
2026/10/1 15:04:32 网站建设 项目流程

excel 大数据量导出:分页查询 + 分 sheet 设置数据

一、适用场景与核心思路

数据量上万甚至几十万时,一次性查询 + 一次性写入 excel 会导致内存溢出、系统卡死,需要「边查边写」。

核心四步:

  1. 分页查询:每页查询固定条数(如 1w),循环拉取,直到返回空或达到总量上限。
  2. 分 sheet 存放:单个 sheet 写满sheetSize行后,切换到下一个 sheet。
  3. 总量上限保护:累计条数超过excelMaxSize直接停止,避免无限导出。
  4. 分页不 count:导出只需要数据、不需要总数,可关闭 count 查询提升性能。

关键参数:

参数说明示例值
pageSize每页查询条数10000
sheetSize单个 sheet 最大行数10000 ~ 100000
excelMaxSize导出总条数上限1000000

二、方式一:POI

/** * 大数据量导出:分页查询 + 分 sheet 写入 * 业务查询条件通过 params 传入,方法内只处理分页参数 * * @param params 业务查询条件 * @param response 响应 * @param formClient 数据查询客户端 */publicvoidexportExcel(JSONObjectparams,HttpServletResponseresponse,FormClientformClient){// 每次分页查询大小intpageSize=10000;// 每个 sheet 的行数intsheetSize=10000;// excel 导出最大值,防止内存溢出longexcelMaxSize=1000000L;// 已读取的数据量longtotal=0;// 当前读取到第几页数据,从 1 开始intcurrent=1;// 第一步,创建一个 workbook,对应一个 Excel 文件HSSFWorkbookhwb=newHSSFWorkbook();// 设置样式HSSFCellStylestyle=setRowStyle(hwb);try{// 缓存全部数据,再按 sheetSize 拆分写入JSONArrayjsonArray=newJSONArray();// 第二步,分页拉取数据do{params.put("pageIndex",current);params.put("pageSize",pageSize);JsonResultdkResult=formClient.queryForJson("zss_qylb_data2",JSONObject.toJSONString(params));JSONObjectdkObject=JSONObject.parseObject(dkResult.toString());JSONArraydkArray=dkObject.getJSONArray("data");if(CollectionUtils.isEmpty(dkArray)){break;}jsonArray.addAll(dkArray);total=current*pageSize+dkArray.size();// 已写入的数据已经超过最大值限制,则退出if(total>=excelMaxSize){break;}// 换下一页current++;}while(true);StringfileName="导出数据";// 第三步,按 sheetSize 拆分,写入多个 sheetif(jsonArray.size()>0){intsize=jsonArray.size()/sheetSize;for(inti=0;i<=size;i++){HSSFSheetsheet=hwb.createSheet("sheet"+(i+1));// 设置表头createRowHead(sheet,style);// 取当前 sheet 的数据区间List<Object>subList=i<size?jsonArray.subList(i*sheetSize,(i+1)*sheetSize):jsonArray.subList(i*sheetSize,jsonArray.size());setRowData(newJSONArray(subList),sheet,style);}}// 第四步,导出ExcelChangeUtil.exportExcelByDownload(hwb,response,fileName);}catch(Exceptionex){logger.error(ExceptionUtil.getExceptionMessage(ex));MessageUtil.triggerException("出错了,请重试!",null);}}// 设置样式privateHSSFCellStylesetRowStyle(HSSFWorkbookhwb){HSSFCellStylestyle=hwb.createCellStyle();style.setAlignment(HorizontalAlignment.CENTER);// 左右居中style.setVerticalAlignment(style.getVerticalAlignmentEnum().CENTER);// 垂直居中style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());// 背景颜色Fontfont=hwb.createFont();font.setFontHeightInPoints((short)11);font.setFontName("宋体");style.setFont(font);returnstyle;}// 创建表头(列按业务自行增删)privatevoidcreateRowHead(HSSFSheetsheet,HSSFCellStylestyle){HSSFRowrow=sheet.createRow(0);String[]heads={"序号","名称","统一社会信用代码","所属县区","行业代码","备注"};for(inti=0;i<heads.length;i++){HSSFCellcell=row.createCell(i);cell.setCellStyle(style);cell.setCellValue(heads[i]);// 部分列宽度需要加长,可单独设置sheet.setColumnWidth(i,i==1?10000:5000);}}// 封装数据(字段按业务自行替换)privatevoidsetRowData(JSONArrayarray,HSSFSheetsheet,HSSFCellStylestyle){for(inti=0;i<array.size();i++){JSONObjectdata=array.getJSONObject(i);HSSFRowrow=sheet.createRow(i+1);HSSFCellc=row.createCell(0);c.setCellStyle(style);c.setCellValue(i+1);c=row.createCell(1);c.setCellStyle(style);c.setCellValue(data.getString("qymc")!=null?data.getString("qymc"):"--");c=row.createCell(2);c.setCellStyle(style);c.setCellValue(data.getString("tyshxydm")!=null?data.getString("tyshxydm"):"--");}}

三、方式二:alibaba-easyexcel

@PostMapping("/exportExcel")publicvoidexportExcel(QueryDataqueryData,HttpServletResponseresponse){// 每次分页查询大小intpageSize=10000;// 每个 sheet 的大小longsheetSize=100000L;// excel 导出最大值longexcelMaxSize=1000000L;// 已读取的数据量longtotal=0;// 当前读取到第几页数据,从 1 开始intcurrent=1;QueryFilterqueryFilter=QueryFilterBuilder.createQueryFilter(queryData);handleFilter(queryFilter);// 重新设置分页控制:分页不 countIPagepage=newcom.baomidou.mybatisplus.extension.plugins.pagination.Page(current,pageSize,false);queryFilter.setPage(page);ExcelWriterexcelWriter=null;try{excelWriter=EasyExcel.write(response.getOutputStream(),Demo.class).build();// 注意:同一个 sheet 只要创建一次WriteSheetwriteSheet=EasyExcel.writerSheet("sheet1").build();do{page.setCurrent(current);getBaseService().query(queryFilter);List<Demo>list=page.getRecords();if(CollectionUtils.isEmpty(list)){break;}excelWriter.write(list,writeSheet);// 如果 sheet 满了,换下一个 sheetif(current>1&&(current*pageSize%sheetSize==0)){writeSheet=EasyExcel.writerSheet("sheet"+((current*pageSize/sheetSize)+1)).build();}total=current*pageSize+list.size();// 已写入的数据已经超过最大值限制,则退出if(total>=excelMaxSize){break;}// 换下一页current++;}while(true);}catch(Exceptionex){MessageUtil.triggerException("出错了,请重试!",null);logger.error(ExceptionUtil.getExceptionMessage(ex));}finally{if(excelWriter!=null){excelWriter.finish();}}}

四、注意事项

  1. sheet 行数上限:HSSFWorkbook(.xls)单 sheet 最多 65536 行,若sheetSize需要更大(如 10w),请改用XSSFWorkbook或 EasyExcel(.xlsx)。
  2. EasyExcel 的 WriteSheet:同一个 sheet 只创建一次;只有切换到新 sheet 时才重新EasyExcel.writerSheet(...).build()。
  3. 分页不 count:new Page(current, pageSize, false)的第三个参数为false时不执行 count 查询,导出场景能明显提速。
  4. 总量估算:total = current * pageSize + list.size()只是估算值(当前页不满时会偏大),仅用于上限判断,不用于精确统计。
  5. 边查边写 vs 全量缓存:POI 示例是把所有分页数据先缓存到jsonArray再按 sheet 拆分;数据量特别大时建议改为边查边写,避免缓存占用过多内存。
  6. ExcelChangeUtil / MessageUtil / formClient为项目内部工具,接入时替换为对应的导出与查询实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询