多sheet excel 导出
在处理大量数据导出到Excel时,将数据分到多个Sheet后统一导出是一种高效且常见的方法。下面用一个流程图帮你快速了解核心流程,然后再看具体的代码实现。
flowchart TDA[开始导出请求] --> B[计算数据总量与Sheet数量]B --> C[创建ExcelWriter实例]C --> D[循环创建每个Sheet]D --> E{是否还有未处理数据?}E -- 是 --> F[分页查询当前Sheet数据]F --> G[创建WriteSheet并设置名称]G --> H[将数据写入当前Sheet]H --> DE -- 否 --> I[执行excelWriter.finish]I --> J[关闭输出流]J --> K[导出完成]
下面是基于 EasyExcel 库的具体实现方案和代码示例。
📝 核心代码实现
首先,在项目的 pom.xml
文件中添加 EasyExcel 依赖。
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version> <!-- 请使用最新稳定版本 -->
</dependency>
下面是实现分 Sheet 导出的关键代码:
@Service
public class DataExportService {@Autowiredprivate YourMapper yourMapper; // 你的数据访问层/*** 将大数据量分Sheet导出为Excel* @param response HttpServletResponse*/public void exportLargeDataToSheets(HttpServletResponse response) {// 1. 设置响应头,告诉浏览器这是一个Excel文件下载请求response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("UTF-8");String fileName = "大数据导出示例.xlsx";try {fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");} catch (UnsupportedEncodingException e) {e.printStackTrace();}response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName);// 2. 获取数据总量,并计算需要的Sheet数long totalCount = yourMapper.selectCount(null); // 假设的方法,获取总数据量int batchSize = 5000; // 每个Sheet存放的数据行数,可调整int sheetCount = (int) (totalCount / batchSize) + (totalCount % batchSize > 0 ? 1 : 0);// 3. 创建ExcelWriter实例ExcelWriter excelWriter = null;try (OutputStream outputStream = response.getOutputStream()) {excelWriter = EasyExcel.write(outputStream, YourDataModel.class).build(); // YourDataModel是你的数据模型类// 4. 循环创建并写入每个Sheetfor (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {// 计算当前页的起始位置long currentPage = sheetIndex + 1;long startRow = sheetIndex * batchSize;// 分页查询数据List<YourDataModel> dataList = yourMapper.selectBatch(startRow, batchSize); // 假设的分页查询方法// 创建WriteSheet,并指定Sheet名称(例如:Sheet1, Sheet2...)WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex, "数据页_" + (sheetIndex + 1)).build();// 将当前批次的数据写入到对应的Sheet中excelWriter.write(dataList, writeSheet);}} catch (IOException e) {e.printStackTrace();} finally {// 5. 非常重要!最终必须调用finish方法,才会真正写出文件,并关闭相关资源。if (excelWriter != null) {excelWriter.finish();}}}
}
💡 代码要点解析
- 数据查询:关键在于分页查询。使用
limit startRow, batchSize
这样的SQL语句,分批从数据库拉取数据,避免一次性加载全部数据导致内存溢出(OOM)。你需要根据使用的持久层框架(如MyBatis)实现对应的分页查询方法。 - Sheet管理:通过循环创建多个
WriteSheet
对象,每个对象对应Excel中的一个工作表。writerSheet(sheetIndex, "Sheet名称")
方法用于指定Sheet的索引和名称。 - 资源管理:
ExcelWriter
和OutputStream
必须正确关闭。在finally
块中调用excelWriter.finish()
是确保资源释放和文件正确生成的关键。
🚀 进阶优化建议
当数据量特别大(例如百万行以上)时,可以考虑以下优化方案:
- 异步导出:对于耗时很长的导出任务,建议采用异步处理。用户触发导出后,服务端生成一个任务ID并立即返回。任务在后台执行,用户可通过任务ID轮询进度或等待完成后下载。
- 多线程并行处理:如果不同Sheet的数据相互独立,可以使用多线程并行查询和写入,充分利用多核CPU能力。但要注意线程安全和数据库连接池压力。
- 限制与压缩:Excel单个Sheet最多支持约104万行数据。如果总数据量远超于此,分Sheet是必须的。最终生成的文件如果过大,可以考虑打包成ZIP格式提供下载。
⚠️ 注意事项
- 内存监控:即使分页查询,也要注意每批数据的大小(
batchSize
),避免单个批次数据过大。建议根据实际数据行的内存占用来调整batchSize
,通常设置在几千到一万条记录为宜。 - 事务与连接:长时间执行的导出任务可能会占用数据库连接。确保查询方法的事务传播行为设置正确(例如
@Transactional(readOnly = true, timeout = 60)
),避免长时间占用写事务。
希望这份详细的指南和代码示例能帮助你顺利实现需求!如果你在具体实现中遇到其他问题,可以随时提出。