概述
Excel导入导出是业务开发中非常常见的需求。本文记录一下如何快速入门使用EasyExcel,深度实战,以及遇到的问题。
入门
使用EasyExcel导入如下依赖即可:
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId>
</dependency>
使用EasyExcel主要是两类场景:
- 上传Excel文件并解析Sheet里数据,数据校验,脏数据清除,数据落库等;
- 从数据库里查询到数据后,下载导出为Excel;
一般而言,实现上面两个需求场景时,都会提供一个工具类
/*** excel 导出** @param fileName fileName* @param fileNameTemplate 需要提供模板* @param data list<PO>* @throws IOException 调用方处理*/
public static ResponseEntity<Resource> excelExport(String fileName, String fileNameTemplate, Object data) throws IOException {/** 填充文件 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 {} 代表普通变量 {.} 代表是list的变量*/InputStream inputStream = getResourcesFileInputStream(fileNameTemplate);fileName = fileName + ".xlsx";ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(inputStream).build();WriteSheet writeSheet = EasyExcel.writerSheet().build();writeSheet.setSheetName(fileName);FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.VERTICAL).build();excelWriter.fill(data, fillConfig, writeSheet);// 关闭流excelWriter.finish();File dbfFile = new File(fileName);if (!dbfFile.exists()) {dbfFile.createNewFile();}HttpHeaders headers = new HttpHeaders();headers.add("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));InputStreamResource resource = new InputStreamResource(new FileInputStream(dbfFile));return ResponseEntity.ok().headers(headers).contentLength(dbfFile.length()).contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8")).body(resource);
}private static InputStream getResourcesFileInputStream(String fileName) {return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName);
}
上传
导出
Controller层接口使用上面的excelExport导出方法:
@ApiOperation(value = "数据统计服务-数据概览-性别", notes = "数据概览-性别")
@PostMapping("/overview/sex/excel")
public ResponseEntity<Resource> overviewSex(HttpServletResponse response, @RequestBody CommonQueryParam param) throws IOException {// 仅作为示例,一般不会在controller层写业务biz逻辑List<SexExcel> sexExcelList = getList(param); return ExcelUtils.excelExport("性别数据", "sexTemplate.xlsx", sexExcelList);
}
然后提供一个sexTemplate.xlsx模板文件,放在classpath路径下面,一般都是src/main/resources
路径下面:
对应于Excel模板文件的POJO实体类定义如下:
@Data
public class SexExcel {private String date;private String source;private String sex;private Long number;
}
需要注意的就是controller层接口请求参数里必须要有HttpServletResponse response
,返回类型得是ResponseEntity<Resource>
基本上入门使用就是上面这些内容。
问题
记录几个在使用EasyExcel时遇到的问题。
NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
背景:之前已离职同事写的屎山代码,使用原生poi导出Excel,问题太多,包括但不限于接口超时,CPU飙高等。
还有个流关闭异常的报错:failed UT010029: Stream is closed
,因为打印日志时使用e.getMessage()
,没有打印出报错堆栈。
网络上关于原生POI的问题不要太多,包括office 2007和2010适配,占用内存高,占用CPU,代码冗长等。
于是,有了用EasyExcel替换原生POI的想法。导入EasyExcel,Debug模式启动应用失败,详细的报错信息如下:
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1054)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:645)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
很常见的报错,参考Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError。NoClassDefFoundError一般是多个版本的三方jar包并存时依赖冲突导致。
检查一下pom.xml
文件,之前使用原生POI导出引入版本号:
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.11</version>
</dependency>
改造引入的EasyExcel-2.2.11,依赖的版本为:
删除poi-3.11
版本解决问题。
ExcelGenerateException: Calling the ‘fill’ method must use a template
报错日志为:
at com.alibaba.excel.write.ExcelBuilderImpl.fill(ExcelBuilderImpl.java:72)
at com.alibaba.excel.ExcelWriter.fill(ExcelWriter.java:185)
at com.aba.common.utils.ExcelUtils.excelExport(ExcelUtils.java:47)
定义好Excel模板,也定义好POJO实体类,但是在业务编码时,会发现另一个拼写更适合一些,比如把name
换成alias
,两边不一致时,就有这个问题。
@Data
public class DaTongUser {private Integer index;private String name;private String mobile;
}
对应的Excel模板文件:
NullPointerException: null
具体的报错信息:
at com.alibaba.excel.write.executor.ExcelWriteFillExecutor.doFill(ExcelWriteFillExecutor.java:191)
at com.alibaba.excel.write.executor.ExcelWriteFillExecutor.fill(ExcelWriteFillExecutor.java:118)
at com.alibaba.excel.write.ExcelBuilderImpl.fill(ExcelBuilderImpl.java:78)
at com.alibaba.excel.ExcelWriter.fill(ExcelWriter.java:185)
at com.aba.common.utils.ExcelUtils.excelExport(ExcelUtils.java:47)