在处理大量数据导出到Excel时,将数据分到多个Sheet后统一导出是一种高效且常见的方法。下面用一个流程图帮你快速了解核心流程,然后再看具体的代码实现。
flowchart TD
A[开始导出请求] --> B[计算数据总量与Sheet数量]
B --> C[创建ExcelWriter实例]
C --> D[循环创建每个Sheet]
D --> E{是否还有未处理数据?}
E -- 是 --> F[分页查询当前Sheet数据]
F --> G[创建WriteSheet并设置名称]
G --> H[将数据写入当前Sheet]
H --> D
E -- 否 --> 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 {
@Autowired
private 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. 循环创建并写入每个Sheet
for (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)
),避免长时间占用写事务。
希望这份详细的指南和代码示例能帮助你顺利实现需求!如果你在具体实现中遇到其他问题,可以随时提出。