SpringBoot一个注解轻松搞定海量Excel导出

最终把百万级数据导出到 Excel 的功能做通了,内存没炸,响应也没有卡死,整个流程只要给个文件名、实体类和一段分片查询逻辑,就能跑起来。

SpringBoot一个注解轻松搞定海量Excel导出

下面把用法和注意点讲清楚,干脆利落,照着做就行。先说想法:别把全量数据一次性往内存里抬,那是把应用往断电口推;正确做法像搬砖分批装车,分段拉数据、流式写入、写完一批就放行,整个过程不落盘、不占大内存,浏览器点下载就能拿到文件。

操作步骤很直接。先在实体类字段上贴注解,用自定义的 @ExcelColumn 标注每列的表头、顺序、宽度、格式等。控制器那边只需要调用一个导出工具,传三个东西:文件名、实体类型和一个分片查询的函数。查询函数负责按批次从数据库拉出数据,工具内部把每批数据交给 EasyExcel 的流式写入器写入响应流,写完一批就释放,下一批继续。这样就不会把全表刷到 JVM 里,也不用先在磁盘上生成中间文件。

SpringBoot一个注解轻松搞定海量Excel导出

说下核心三点:EasyExcel 的流式写(基于 SXSSF),自定义注解做列映射,分片查询。这三样搭在一起就能稳稳当当干活。EasyExcel 丢进项目的依赖里已经带着 POI,项目里一般不用再单独加 poi 的包,避免版本冲突。流式写的好处是写入时只保留一个“活动窗口”的行数,不会把整张表全部放内存。分片查询则避免 OFFSET 导致的全表扫描,用 ID 段查询取代 LIMIT OFFSET,性能更稳。

实体类怎么处理举个例子:User 实体有 id、name、phone、email、createTime,你就在这些字段上加 @ExcelColumn,注解里写列名和顺序。导出工具启动时会解析这些注解,生成列映射关系。注解还能带列宽和格式规则,导出时把这些设定交给写入器。后来要加新列,直接在实体上加注解就行,导出逻辑不用改。

SpringBoot一个注解轻松搞定海量Excel导出

分片查询的细节要注意。别用 offset/page 这套,正确的做法是按主键做区间查。伪代码思路是:记录上次的最后 id,下一次查询条件用 id > lastId,order by id asc,limit batchSize。MyBatis-Plus 用起来挺方便,条件里写 gt(id, lastId) 加上 orderByAsc(id) 和 limit。循环拉数据直到查不到为止。batchSize 要调合适,别太大也别太小,既保证单次内存可控,又能减少数据库往返次数。还有一点,查询时只拉导出需要的字段,做成轻量 DTO,别把大对象整进来,内存会吃不消。

写入环节也分几步走:先解析注解生成表头,创建 EasyExcel 的流式写入器并绑定响应输出流,循环从 DB 取数据,一批一批写入,写完当前批次就 flush,继续下一批。响应头提前设置好,防止中文文件名乱码和浏览器缓存问题。最后一批写完要把 writer 和输出流都关了。遇到异常要保证流能被正确释放,别把文件句柄和连接给泄露了。把这些点都封到工具里,调用者只关心查询函数就行。

为了好看和兼容,我加了自定义的 CellWriteHandler,负责列宽、字体、表头背景和对齐等样式。实现方式也不复杂:从注解里读宽度配置,在列级别设置宽度;表头样式在写表头时统一应用;正文根据字段类型做格式化,像日期字段按 yyyy-MM-dd HH:mm:ss 格式写。把样式聚焦在 handler 里,维护起来也方便。

性能方面的经验是实践出来的。单次查询别返回太多行,哪怕写入是流式的,单批数据对象还是会占 JVM 内存。尽量避免复杂 join 和大范围排序,需要时把导出数据事先做成投影表或轻量的 DTO。数据库端索引要到位,ID 段查依赖主键的顺序扫描,别在无索引的字段上做范围过滤。再有就是不要把导出操作和线上交易库放在同一实例上,高峰期容易相互影响。

遇到的坑也说两句,免得踩雷。第一,有的字段注解里列宽配得太离谱,生成的 xlsx 文件反而大;第二,数据库返回字段顺序和注解顺序不一致会导致列错位,这一般是注解解析实现不严谨造成的,要严格按注解定义的序号去映射;第三,一些老旧浏览器对流式下载兼容性差,需要调整 content-disposition 和 content-type 头来适配。再提醒一句,导出逻辑里尽量不要拉太多冗余字段,这东西会像雪球一样越滚越大。

异常和恢复策略也要写清楚。导出期间可能遇到数据库超时、网络断连或流写异常。工具里要把这些错误边界处理好:查询失败就终止写入并把错误返回给前端;写流出错时捕获异常并清理资源,记录最后成功的 batchId 或 lastId,方便后续做断点续传。日志里最好记录批次号和最后成功 id,出问题时从断点恢复比重跑省心得多。

部署时把参数暴露出来,这样不同环境可以调整窗口大小和 batchSize。依赖版本要管好,easyexcel 会带 poi,避免重复引入。如果要支持复杂表头或多表合并导出,可以在注解里扩展元数据,解析器按规则生成多行表头。调试阶段先用小数据集跑通流程,确认样式和列映射没问题再跑全量导出。

最后说一句实用的操作顺序:把实体上好注解,写一个简单的查询 lambda(给 lastId 和 batchSize,返回 List),把文件名、实体类型和这个 lambda 传给导出工具,工具会把剩下的事都干完。调试好了再放到接口上,记得做监控和日志,出问题能快速定位。

© 版权声明

相关文章

暂无评论

none
暂无评论...