1. 项目概述
在Java企业级应用开发中,Excel数据的导入导出是高频需求场景。传统基于Apache POI的方案存在内存消耗大、API复杂等问题。阿里巴巴开源的EasyExcel通过创新的设计解决了这些痛点,特别适合与Spring Boot框架集成使用。
我在多个电商和ERP系统中实际应用这套技术方案,处理过单文件百万级数据量的导入导出任务。相比传统方案,EasyExcel+Spring Boot的组合可以降低约70%的内存占用,同时代码可读性提升明显。下面将分享这套方案的完整实现细节和实战经验。
2. 技术选型分析
2.1 为什么选择EasyExcel
EasyExcel的核心优势体现在三个方面:
- 内存优化:采用逐行解析的流式模式,理论上可以处理无限大的Excel文件
- API简洁:通过注解驱动的方式,避免了POI复杂的API调用链
- 扩展性强:支持自定义格式转换、多sheet处理等高级特性
2.2 Spring Boot集成优势
Spring Boot的自动配置特性可以简化EasyExcel的集成:
- 自动注册Converter组件
- 简化文件上传下载的配置
- 与Spring MVC无缝对接
- 内置的异常处理机制
3. 环境准备
3.1 依赖配置
在pom.xml中添加以下依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>3.2 实体类注解配置
定义数据模型时使用EasyExcel注解:
@Data public class UserData { @ExcelProperty("用户ID") private Long id; @ExcelProperty("用户名") private String name; @ExcelProperty(value = "创建时间", converter = LocalDateTimeConverter.class) private LocalDateTime createTime; }4. 导出功能实现
4.1 基础导出实现
核心导出代码如下:
@GetMapping("/export") public void exportExcel(HttpServletResponse response) throws IOException { response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=users.xlsx"); List<UserData> dataList = getUserData(); EasyExcel.write(response.getOutputStream(), UserData.class) .sheet("用户数据") .doWrite(dataList); }4.2 高级导出特性
- 自定义样式:
WriteCellStyle headStyle = new WriteCellStyle(); headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); headStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); WriteCellStyle contentStyle = new WriteCellStyle(); contentStyle.setWrapped(true); HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headStyle, contentStyle); EasyExcel.write(outputStream) .registerWriteHandler(styleStrategy) //...- 动态列导出:
ExcelWriter excelWriter = EasyExcel.write(outputStream).build(); WriteSheet sheet1 = EasyExcel.writerSheet(0, "Sheet1") .head(headList) .build(); excelWriter.write(dataList, sheet1); excelWriter.finish();5. 导入功能实现
5.1 基础导入实现
定义监听器:
public class UserDataListener extends AnalysisEventListener<UserData> { private static final int BATCH_COUNT = 100; private List<UserData> cachedList = new ArrayList<>(BATCH_COUNT); @Override public void invoke(UserData data, AnalysisContext context) { cachedList.add(data); if (cachedList.size() >= BATCH_COUNT) { saveData(); cachedList.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); } private void saveData() { // 批量保存逻辑 } }控制器代码:
@PostMapping("/import") public String importExcel(@RequestParam("file") MultipartFile file) { try { EasyExcel.read(file.getInputStream(), UserData.class, new UserDataListener()) .sheet() .doRead(); return "导入成功"; } catch (IOException e) { return "导入失败"; } }5.2 复杂导入处理
- 数据校验:
@Override public void invoke(UserData data, AnalysisContext context) { // 手机号格式校验 if (!Pattern.matches("^1[3-9]\\d{9}$", data.getPhone())) { throw new ExcelAnalysisException("手机号格式错误"); } // ... }- 异常处理:
@ExceptionHandler(ExcelAnalysisException.class) public ResponseEntity<String> handleExcelException(ExcelAnalysisException e) { return ResponseEntity.badRequest().body(e.getMessage()); }6. 性能优化实践
6.1 大数据量处理
对于百万级数据导出:
// 分页查询数据 PageHelper.startPage(pageNum, pageSize); List<UserData> data = userMapper.selectAll(); // 使用模板方式导出 String template = "template.xlsx"; EasyExcel.write(fileName) .withTemplate(template) .sheet() .doWrite(data);6.2 内存控制技巧
- 设置JVM参数:
-XX:+UseG1GC -Xms512m -Xmx1024m- 使用临时文件缓存:
EasyExcel.write(fileName) .inMemory(false) // 启用磁盘缓存 // ...7. 常见问题解决方案
7.1 日期格式问题
自定义日期转换器:
public class LocalDateTimeConverter implements Converter<LocalDateTime> { @Override public Class<LocalDateTime> supportJavaTypeKey() { return LocalDateTime.class; } @Override public LocalDateTime convertToJavaData(ReadConverterContext<?> context) { return LocalDateTime.parse(context.getReadCellData().getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } }7.2 中文乱码处理
在Spring Boot配置中添加:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB响应头设置:
response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Type", "application/vnd.ms-excel;charset=UTF-8");8. 扩展应用场景
8.1 动态模板导出
- 准备模板文件:
姓名: ${name} 部门: ${dept}- Java填充代码:
Map<String, Object> data = new HashMap<>(); data.put("name", "张三"); data.put("dept", "研发部"); EasyExcel.write(outputStream) .withTemplate("template.xlsx") .sheet() .doFill(data);8.2 多Sheet导出
ExcelWriter excelWriter = EasyExcel.write(fileName).build(); // Sheet1 WriteSheet sheet1 = EasyExcel.writerSheet(0, "用户信息") .head(UserData.class) .build(); excelWriter.write(userList, sheet1); // Sheet2 WriteSheet sheet2 = EasyExcel.writerSheet(1, "部门信息") .head(DeptData.class) .build(); excelWriter.write(deptList, sheet2); excelWriter.finish();在实际项目中,我发现合理使用EasyExcel的缓存机制可以显著提升性能。对于10万行以上的数据导出,建议设置inMemory(false)参数启用磁盘缓存,这样可以将内存占用控制在稳定水平。同时,对于复杂格式的导出需求,提前准备Excel模板比完全用代码构建样式更高效。