使用EasyExcel实现多Sheet数据导出与Web端下载的完整指南
1. 为什么选择EasyExcel处理Excel数据在Java生态中处理Excel文件很多开发者第一时间会想到Apache POI。这个老牌工具确实功能强大但我在实际项目中发现当处理超过10万行数据时POI经常会出现内存溢出OOM问题。有次凌晨两点我还在处理一个导出报表导致服务器崩溃的紧急问题最后发现就是POI的内存问题导致的。EasyExcel作为阿里开源的组件最突出的优势就是内存优化。它采用逐行解析的机制不同于POI的全量加载模式。我做过实测导出50万行数据时POI需要约1.5GB内存而EasyExcel仅需200MB左右。这对于Web应用来说简直是救命稻草——你再也不用担心导出大文件时把服务器内存撑爆了。另一个让我选择EasyExcel的原因是API设计更人性化。POI的操作就像组装一台精密仪器需要处理各种Workbook、Sheet、Row、Cell对象。而EasyExcel的链式调用就像搭积木比如创建一个带格式的单元格POI需要10行代码EasyExcel只需要1行.registerWriteHandler(new CustomStyleHandler())。特别在Web导出场景下EasyExcel与Spring的整合体验非常流畅。上周我刚用以下代码帮同事快速实现了下载功能GetMapping(/export) public void export(HttpServletResponse response) { response.setHeader(Content-Disposition, attachment;filenamedata.xlsx); EasyExcel.write(response.getOutputStream(), User.class) .sheet(用户列表) .doWrite(userService.list()); }2. 项目配置与基础准备2.1 添加Maven依赖建议使用最新稳定版目前是3.3.2新版本在并发处理和样式设置上有明显优化。除了核心依赖我通常会额外引入这两个工具包dependencies !-- 核心库 -- dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency !-- 用于日期格式化 -- dependency groupIdjoda-time/groupId artifactIdjoda-time/artifactId version2.10.14/version /dependency !-- 简化Lombok注解 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies2.2 实体类定义技巧用ExcelProperty注解定义导出字段时有个容易踩的坑属性顺序决定列顺序。有次我的导出文件列序错乱排查半天才发现是字段声明顺序和注解顺序不一致。推荐这样定义实体Data // 使用Lombok简化getter/setter public class Product { ExcelProperty(value 产品ID, index 0) private Long id; ExcelProperty(value 产品名称, index 1) private String name; DateTimeFormat(yyyy-MM-dd HH:mm) ExcelProperty(value 生产日期, index 2) private Date productionDate; }几个实用技巧用index显式指定列顺序更可靠日期字段建议配合DateTimeFormat注解复杂表头可以用数组表示ExcelProperty({一级标题, 二级标题})3. 实现多Sheet导出3.1 基础多Sheet导出先看一个典型的多Sheet导出场景我们需要把销售数据按地区拆分到不同Sheet。关键点是复用ExcelWriter实例public void multiSheetExport() { String fileName sales_report_ System.currentTimeMillis() .xlsx; try (ExcelWriter excelWriter EasyExcel.write(fileName).build()) { // Sheet1: 华东地区数据 WriteSheet eastChinaSheet EasyExcel.writerSheet(0, 华东) .head(SalesData.class) .build(); excelWriter.write(getEastChinaData(), eastChinaSheet); // Sheet2: 华南地区数据 WriteSheet southChinaSheet EasyExcel.writerSheet(1, 华南) .registerWriteHandler(new HighlightOver100kHandler()) .head(SalesData.class) .build(); excelWriter.write(getSouthChinaData(), southChinaSheet); } }这里我特意加了两个实用技巧文件名添加时间戳避免重复为华南数据注册了自定义样式处理器高亮销售额10万的记录3.2 动态Sheet生成实际业务中我们经常需要根据数据动态生成Sheet。比如按月份自动分Sheet的场景public void dynamicSheetsExport(ListMonthlyData allData) { ExcelWriter excelWriter EasyExcel.write(dynamic_sheets.xlsx).build(); try { int sheetNo 0; for (MonthlyData monthly : allData) { WriteSheet sheet EasyExcel.writerSheet(sheetNo, monthly.getMonthName()) .head(DataItem.class) .registerWriteHandler(new AutoColumnWidthHandler()) .build(); excelWriter.write(monthly.getItems(), sheet); } } finally { if (excelWriter ! null) { excelWriter.finish(); } } }重要提醒一定要在finally块中关闭excelWriter否则可能导致文件损坏。我就曾因为忘记关闭流导致下载的Excel文件无法打开。4. Web端下载实现4.1 基础下载实现结合Spring Boot实现下载时这几个响应头设置很重要GetMapping(/download/multi-sheet) public void download(HttpServletResponse response) throws IOException { String fileName URLEncoder.encode(多页数据报表, UTF-8); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); response.setHeader(Content-disposition, attachment;filename*utf-8 fileName .xlsx); ExcelWriter writer EasyExcel.write(response.getOutputStream()).build(); // Sheet1 writer.write(getUserData(), EasyExcel.writerSheet(0, 用户信息) .head(User.class) .build()); // Sheet2 writer.write(getProductData(), EasyExcel.writerSheet(1, 产品列表) .head(Product.class) .registerWriteHandler(new HeaderColorHandler(Color.GRAY)) .build()); writer.finish(); }注意几个关键点filename*utf-8是RFC 5987规范写法能更好支持中文文件名设置正确的MIME类型很重要xlsx对应的是application/vnd.openxmlformats-officedocument.spreadsheetml.sheet输出流不需要手动关闭writer.finish()会自动处理4.2 大文件下载优化当数据量特别大时比如超过50万行建议采用分页查询分批写入的方式GetMapping(/export-large) public void exportLargeData(HttpServletResponse response) throws IOException { response.setHeader(Content-disposition, attachment;filenamelarge_data.xlsx); ExcelWriter writer EasyExcel.write(response.getOutputStream()) .head(LargeData.class) .build(); WriteSheet sheet EasyExcel.writerSheet(大数据量).build(); int page 1; while (true) { PageLargeData dataPage dataService.getByPage(page, 5000); if (dataPage.isEmpty()) { break; } writer.write(dataPage.getContent(), sheet); page; } writer.finish(); }这种方案有两个优势内存占用稳定不会因为数据量增大而OOM用户能更早开始下载流式写入5. 高级功能与避坑指南5.1 自定义样式处理通过WriteHandler接口可以自定义各种样式。比如要给表头加背景色public class HeaderStyleHandler extends AbstractColumnWidthStyleStrategy { Override protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) { CellStyle style cell.getSheet().getWorkbook().createCellStyle(); style.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(style); } }使用时注册即可EasyExcel.write(outputStream) .registerWriteHandler(new HeaderStyleHandler()) // ...5.2 常见问题排查问题1下载的文件损坏无法打开检查是否调用了excelWriter.finish()确保没有在Controller方法中使用ResponseBody问题2中文文件名乱码确保使用URLEncoder.encode(fileName, UTF-8)检查响应头是否包含filename*utf-8前缀问题3样式不生效确认WriteHandler注册顺序后注册的会覆盖前者检查是否在正确的WriteSheet上注册5.3 性能优化建议对于固定样式的报表建议复用ExcelWriter配置对象大量相似单元格样式时使用CellStylePool减少内存消耗启用缓存EasyExcel.write().useDefaultStyleCache(true)合并单元格操作尽量放在最后执行
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2442588.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!