SpringBoot整合最强Excel处理工具EasyExcel来袭!彻底告别内存溢出
- 2026-09-20 10:12:29
SpringBoot整合最强Excel处理工具EasyExcel来袭!彻底告别内存溢出哈喽,各位小伙伴~ 前面我们讲了 EasyPoi,用注解驱动的方式快速实现了 Excel 导入导出。但有小伙伴反馈:数据量一大(10万+行),程序就 OOM 了,导出时内存直接爆了! 今天给大家带来的是阿里开源的EasyExcel——Java 领域处理 Excel 的性能天花板。它解决了 POI 最大的痛点:内存溢出。 官方宣称,100 万行数据仅占用几十 MB 内存,这得益于其逐行读取 + 分批写入的设计理念。 希望对小伙伴们有帮助,废话不多说,直接开干,所有的代码都可以直接复制使用。 EasyExcel 核心原理:为什么它能解决 OOM 问题 使用 EasyExcel 实现大数据量导出(支持分批写入,避免内存溢出) 使用 EasyExcel 实现大数据量导入(监听器模式,逐行处理) 导入时处理日期格式、科学计数法 POI 的痛点:Workbook将整个 Excel 加载到内存,10 万行数据轻松吃掉 500MB+ 内存,频繁引发 Full GC 甚至 OOM EasyExcel 的方案:采用SAX 模式(逐行解析)+ 分批刷新,100 万行数据仅需几十 MB 内存 性能对比:官方测试 75MB 的 Excel 文件,POI 需要约 1.5GB 内存,EasyExcel 仅需 64MB 💡一句话总结:如果数据量超过 5 万行,EasyExcel 是你的唯一选择。 








一、本篇你能学到
二、为什么要用?
三、技术实战(基于 Spring Boot 3.5.11 + JDK17)
3.1 核心Mavn依赖
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.3</version></dependency>
3.2 核心配置文件
server:port: 8080spring:servlet:multipart:enabled: truemax-file-size: 500MBmax-request-size: 500MB
3.3 核心代码
实体类
@Data@ContentRowHeight(20)@HeadRowHeight(30)public class OrderExportDto {@ExcelProperty(value = {"订单信息", "订单号"}, index = 0)@ColumnWidth(25)private String orderSn;@ExcelProperty(value = {"订单信息", "创建时间"}, index = 1)@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ColumnWidth(20)private Date createTime;@ExcelProperty(value = {"订单信息", "订单金额"}, index = 2)@NumberFormat("#.##")@ColumnWidth(15)private BigDecimal totalAmount;@ExcelProperty(value = {"订单信息", "订单状态"}, index = 3)@ColumnWidth(12)private String orderStatus;@ExcelProperty(value = {"收货信息", "收货人"}, index = 4)@ColumnWidth(15)private String receiverName;@ExcelProperty(value = {"收货信息", "手机号"}, index = 5)@ColumnWidth(15)private String receiverPhone;@ExcelProperty(value = {"收货信息", "收货地址"}, index = 6)@ColumnWidth(40)private String receiverAddress;@ExcelIgnoreprivate String internalRemark;}
@Datapublic class OrderImportDto {@ExcelProperty(value = {"订单号"})private String orderSn;@ExcelProperty(value = {"创建时间"})@DateTimeFormat("yyyy-MM-dd HH:mm:ss")private Date createTime;@ExcelProperty(value = {"订单金额"})@NumberFormat("#.##")private BigDecimal totalAmount;@ExcelProperty(value = {"订单状态"})private String orderStatus;@ExcelProperty(value = {"收货人"})private String receiverName;@ExcelProperty(value = {"手机号"})private String receiverPhone;@ExcelProperty(value = {"收货地址"})private String receiverAddress;}
注解参数说明:
@ExcelProperty 列名 / 多级表头 / 列索引@ColumnWidth 列宽@ContentRowHeight 内容行高@HeadRowHeight 表头行高@DateTimeFormat 日期格式@NumberFormat 数字格式@ExcelIgnore 忽略字段
监听器
@Slf4jpublic class OrderImportListener implements ReadListener<OrderImportDto> {private static final int BATCH_SIZE = 1000;private final List<OrderImportDto> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_SIZE);private final OrderDataProcessor processor;@Getterprivate int totalCount = 0;public OrderImportListener(OrderDataProcessor processor) {this.processor = processor;}/*** 每解析一行数据调用一次*/@Overridepublic void invoke(OrderImportDto data, AnalysisContext context) {cachedDataList.add(data);totalCount++;if (cachedDataList.size() >= BATCH_SIZE) {saveData();cachedDataList.clear();}}/*** 所有数据解析完成后调用*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {if (!cachedDataList.isEmpty()) {saveData();cachedDataList.clear();}log.info("Excel 导入完成,共处理 {} 条数据", totalCount);}private void saveData() {log.info("批量保存 {} 条数据", cachedDataList.size());processor.process(cachedDataList);}/*** 数据处理器接口,由业务层实现*/public interface OrderDataProcessor {void process(List<OrderImportDto> dataList);}}
Service 层
@Slf4j@Servicepublic class EasyExcelService {/*** 大数据量导出(分批写入,避免 OOM)* 核心思路:* 1. 创建 ExcelWriter(只创建一次)* 2. 分页查询数据,每次写入一批后释放引用* 3. 最后调用 finish() 刷出剩余数据*/public void exportLargeData(HttpServletResponse response) throws IOException {String fileName = "订单数据_" + System.currentTimeMillis() + ".xlsx";response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("UTF-8");String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");response.setHeader("Content-Disposition","attachment; filename=\"" + encodedFileName + "\"");// 1. 创建 ExcelWriter(整个导出过程只创建一次)ExcelWriter excelWriter = null;try {excelWriter = EasyExcel.write(response.getOutputStream(), OrderExportDto.class).excelType(ExcelTypeEnum.XLSX).build();// 2. 创建 SheetWriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();// 3. 模拟分页查询数据库并分批写入int pageSize = 5000;int totalPages = 20; // 总共 10 万条数据for (int page = 0; page < totalPages; page++) {List<OrderExportDto> pageData = queryOrders(page, pageSize);excelWriter.write(pageData, writeSheet);log.info("已写入第 {} 页数据,共 {} 条", page + 1, pageData.size());pageData.clear();}log.info("导出完成,共 {} 页数据,合计 {} 条", totalPages, totalPages * pageSize);} finally {// 4. 关闭 Writer,刷出剩余数据if (excelWriter != null) {excelWriter.finish();}}}/*** 大数据量导入(监听器模式,逐行处理)* 核心思路:* 1. 创建数据处理器(批量入库逻辑)* 2. new 一个监听器实例(不能用 Spring 单例)* 3. EasyExcel.read() 逐行解析,每行调用 invoke()*/public int importLargeData(MultipartFile file) throws IOException {// 1. 创建数据处理器OrderImportListener.OrderDataProcessor processor = dataList -> {for (OrderImportDto dto : dataList) {log.debug("保存订单: {}", dto.getOrderSn());}};// 2. 创建监听器(每次 new 新实例)OrderImportListener listener = new OrderImportListener(processor);// 3. 开始读取(多级表头需指定 headRowNumber=2)EasyExcel.read(file.getInputStream(), OrderImportDto.class, listener).sheet().headRowNumber(2).doRead();return listener.getTotalCount();}/*** 模拟分页查询数据库*/private List<OrderExportDto> queryOrders(int page, int size) {List<OrderExportDto> list = new ArrayList<>();String[] statuses = {"待付款", "待发货", "已发货", "已完成", "已取消"};for (int i = 0; i < size; i++) {int seq = page * size + i;OrderExportDto dto = new OrderExportDto();dto.setOrderSn("ORD" + String.format("%012d", seq));dto.setCreateTime(new Date());dto.setTotalAmount(new BigDecimal(Math.random() * 1000 + 50).setScale(2, RoundingMode.HALF_UP));dto.setOrderStatus(statuses[seq % statuses.length]);dto.setReceiverName("收货人_" + seq);dto.setReceiverPhone("138" + String.format("%08d", seq));dto.setReceiverAddress("XX省XX市XX区XX路" + seq + "号");list.add(dto);}return list;}}
Controller层
@Slf4j@Tag(name = "EasyExcel 测试接口", description = "大数据量 Excel 导入导出")@RestController@RequestMapping("/api/easyexcel")public class EasyExcelController {private final EasyExcelService easyExcelService;public EasyExcelController(EasyExcelService easyExcelService) {this.easyExcelService = easyExcelService;}/*** 大数据量导出(分批写入,10 万行,避免 OOM)*/@Operation(summary = "导出订单数据(10万行,分批写入)")@GetMapping("/export")public void exportLargeData(HttpServletResponse response) {try {easyExcelService.exportLargeData(response);} catch (Exception e) {log.error("导出失败", e);try {// 后期response.setContentType("application/json");response.setCharacterEncoding("UTF-8");response.getWriter().write("{\"code\":500,\"message\":\"导出失败: "+ e.getMessage() + "\"}");} catch (Exception ex) {log.error("写入错误响应失败", ex);}}}/*** 大数据量导入(监听器模式,逐行处理)*/@Operation(summary = "导入订单数据(监听器模式)")@PostMapping("/import")public Result<Integer> importLargeData(@RequestParam("file") MultipartFile file) {try {int count = easyExcelService.importLargeData(file);return Result.success("导入成功,共 " + count + " 条数据", count);} catch (Exception e) {log.error("导入失败", e);return Result.error("导入失败: " + e.getMessage());}}}
四、实战测试
4.1 正常启动

4.2 测试
工具使用Apifox
导出




导入



总结:
结合上面的测试,功能都是正常可以使用的。
最后:

本文完整代码,后台回复【代码】即可领取。目录:easypoi/day02
我还整理了 Java、SpringBoot 学习视频,
后台回复【学习资料】免费获取。
后续会持续更新后端实战干货、bug 解决方案、面试技巧。
关注我,每天进步一点点,写稳代码,少踩坑、少掉头发~
本文来自网友投稿或网络内容,如有侵犯您的权益请联系我们删除,联系邮箱:wyl860211@qq.com 。