SpringBoot 整合 EasyExcel 实现百万级Excel导入导出实战
【摘要】 摘要:在后台管理系统中,Excel导入导出是非常高频的需求。Apache POI 容易出现内存溢出、代码冗余的问题,本文基于 SpringBoot + EasyExcel 实现高性能Excel读写,封装通用监听器、统一返回结果,提供导入校验、分批处理、导出大数据的完整示例,同时讲解避坑要点,适合后台报表、数据批量录入场景直接落地。
前言
日常开发里,用户经常需要批量导入业务数据、导出报表。原生POI操作Excel上手繁琐,读取大文件时会把全部数据加载到堆内存,很容易OOM。阿里开源的EasyExcel对POI做了封装,采用Sax逐行读取,低内存占用,API简洁,是目前Java生态里Excel处理的首选方案。
本文搭建一套可复用的导入导出模板,包含实体映射、自定义监听器、参数校验、异常捕获、大数据分页导出,代码可以直接复制到项目使用。
一、环境依赖
Maven pom.xml 引入依赖,注意版本适配SpringBoot:
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二、定义Excel映射实体
使用@ExcelProperty注解绑定表头名称,@ExcelIgnore忽略不需要导出的字段:
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.ExcelIgnore;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserExcelDTO {
@ExcelProperty("用户名")
private String username;
@ExcelProperty("手机号")
private String phone;
@ExcelProperty("邮箱")
private String email;
@ExcelProperty("状态")
private Integer status;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@ExcelIgnore
private Long id;
}
三、通用导入监听器(重点)
EasyExcel读取需要自定义监听器,每读取一行回调一次,避免一次性加载全部数据。这里做批量攒批入库,减少数据库IO:
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class CommonExcelListener<T> extends AnalysisEventListener<T> {
private final List<T> cacheList = new ArrayList<>();
private static final int BATCH_SIZE = 500;
private final Consumer<List<T>> saveFunc;
public CommonExcelListener(Consumer<List<T>> saveFunc) {
this.saveFunc = saveFunc;
}
@Override
public void invoke(T data, AnalysisContext context) {
cacheList.add(data);
// 攒够批次执行入库
if (cacheList.size() >= BATCH_SIZE) {
saveFunc.accept(cacheList);
cacheList.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 读取结束,处理剩余数据
if (!cacheList.isEmpty()) {
saveFunc.accept(cacheList);
cacheList.clear();
}
}
}
四、Controller 导入&导出接口
import com.alibaba.excel.EasyExcel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelController {
// 模拟service
private void batchSaveUser(List<UserExcelDTO> list) {
// userService.batchInsert(list);
System.out.println("批量入库:" + list.size() + "条");
}
/**
* Excel导入
*/
@PostMapping("/import")
public String importExcel(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), UserExcelDTO.class, new CommonExcelListener<UserExcelDTO>(this::batchSaveUser))
.sheet()
.doRead();
return "导入成功";
}
/**
* Excel导出
*/
@GetMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("用户数据报表", StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
// 模拟从数据库分页查询数据
// List<UserExcelDTO> dataList = userService.listAll();
List<UserExcelDTO> dataList = List.of();
EasyExcel.write(response.getOutputStream(), UserExcelDTO.class)
.sheet("用户列表")
.doWrite(dataList);
}
}
五、大数据导出优化思路
如果数据量上万甚至几十万条,一次性查询会压垮数据库和内存:
- 分页查询,分批写入Excel,不用一次性组装全量List;
- 异步导出:后台任务生成文件,生成完成后通知用户下载;
- 关闭自动表头样式、关闭额外格式,提升写入速度;
- 优先使用xlsx格式,不推荐老旧xls(单sheet上限65535行)。
六、常见踩坑总结
- 表头顺序问题:
@ExcelProperty默认按字段顺序,指定index可以固定列顺序; - 日期转换失败:日期类型需要额外配置转换器;
- 文件上传空指针:前端form必须设置
enctype="multipart/form-data"; - 导出文件名乱码:一定要做URLEncoder编码,兼容Chrome、Edge;
- 监听器不能交给Spring管理:监听器每次读取新建实例,不要加@Component,需要注入Service用构造传入。
七、总结
EasyExcel通过Sax解析模式,从根源解决POI大文件OOM痛点,封装好监听器之后,后续所有Excel导入都可以复用这套代码。在后台管理系统里,导入导出属于标准通用能力,把这部分能力抽成工具类之后,后续业务开发效率会提升很多。本文代码开箱即用,大家可以基于这个模板扩展数据校验、错误行回写、导出字典翻译等能力。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)