overview
Excel import and export is a very common requirement in business development. This article records how to quickly start using EasyExcel, in-depth actual combat, and problems encountered.
getting Started
Use EasyExcel to import the following dependencies:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
There are mainly two types of scenarios for using EasyExcel:
- Upload the Excel file and analyze the data in the Sheet, data verification, dirty data clearing, data storage, etc.;
- After querying the data from the database, download and export it to Excel;
Generally speaking, when implementing the above two requirement scenarios, a tool class will be provided
/**
* 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);
}
upload
export
The Controller layer interface uses the above excelExport export method:
@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);
}
Then provide a sexTemplate.xlsx template file, put it under the classpath path, generally src/main/resources
under the path:
The definition of the POJO entity class corresponding to the Excel template file is as follows:
@Data
public class SexExcel {
private String date;
private String source;
private String sex;
private Long number;
}
It should be noted that the controller layer interface request parameters must have HttpServletResponse response
, and the return type must beResponseEntity<Resource>
Basically, the entry-level use is the above content.
Advanced
Dynamically add self-incrementing serial number column
The effect you want to achieve:
to implement the scheme, add a custom RowWriteHandler:
private static class CustomRowWriteHandler implements RowWriteHandler {
private static final String FIRST_CELL_NAME = "序号";
/**
* 列号
*/
private int count = 1;
@Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer integer, Integer integer1, Boolean aBoolean) {
}
@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer integer, Boolean aBoolean) {
Cell cell = row.createCell(0);
if (row.getRowNum() == 0) {
cell.setCellValue(FIRST_CELL_NAME);
}
cell.setCellValue(++count);
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer integer, Boolean aBoolean) {
}
}
Register this RowWriteHandler:
ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(inputStream).registerWriteHandler(new CustomRowWriteHandler()).build();
question
Record several problems encountered when using EasyExcel.
NoClassDefFoundError: org/apache/poi/util/DefaultTempFileCreationStrategy
Background: The Shishan code written by a colleague who has left the company before uses the native poi to export Excel. There are too many problems, including but not limited to interface timeout, CPU soaring, etc.
There is also an error reporting that the stream is closed abnormally: failed UT010029: Stream is closed
, because it is used when printing the log e.getMessage()
, and the error stack is not printed.
There are not too many problems about native POI on the Internet, including office 2007 and 2010 adaptation, high memory usage, CPU usage, lengthy code, etc.
So, I had the idea of replacing native POI with EasyExcel. Import EasyExcel, Debug mode fails to start the application, the detailed error information is as follows:
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)
For very common errors, refer to NoClassDefFoundError, ClassNotFoundException, NoSuchMethodError in Java learning . NoClassDefFoundError is generally caused by dependency conflicts when multiple versions of three-party jar packages coexist.
Check pom.xml
the file, before using the native POI export to import the version number:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.11</version>
</dependency>
The EasyExcel-2.2.11 introduced by the transformation, the dependent version is:
delete poi-3.11
the version to solve the problem.
ExcelGenerateException: Calling the ‘fill’ method must use a template
The error log is:
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)
The Excel template is defined, and the POJO entity class is also defined. However, when coding the business, you will find that another spelling is more suitable. For example, when the two sides are inconsistent, there will be this problem name
.alias
@Data
public class DaTongUser {
private Integer index;
private String name;
private String mobile;
}
Corresponding Excel template file:
NullPointerException: null
Specific error message:
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)
The version numbers used are:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.11</version>
</dependency>
The source code of the error is as follows:
public void fill(Object data, FillConfig fillConfig) {
if (data == null) {
data = new HashMap<String, Object>(16);
}
if (fillConfig == null) {
fillConfig = FillConfig.builder().build(true);
}
fillConfig.init();
Object realData;
if (data instanceof FillWrapper) {
FillWrapper fillWrapper = (FillWrapper) data;
currentDataPrefix = fillWrapper.getName();
realData = fillWrapper.getCollectionData();
} else {
realData = data;
currentDataPrefix = null;
}
currentUniqueDataFlag = uniqueDataFlag(writeContext.writeSheetHolder(), currentDataPrefix);
// processing data
if (realData instanceof Collection) {
// 报错根源
List<AnalysisCell> analysisCellList = readTemplateData(templateCollectionAnalysisCache);
Collection collectionData = (Collection) realData;
if (CollectionUtils.isEmpty(collectionData)) {
return;
}
Iterator iterator = collectionData.iterator();
if (WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection()) && fillConfig.getForceNewRow()) {
shiftRows(collectionData.size(), analysisCellList);
}
while (iterator.hasNext()) {
// 报错方法
doFill(analysisCellList, iterator.next(), fillConfig, getRelativeRowIndex());
}
} else {
doFill(readTemplateData(templateAnalysisCache), realData, fillConfig, null);
}
}
private List<AnalysisCell> readTemplateData(Map<String, List<AnalysisCell>> analysisCache) {
List<AnalysisCell> analysisCellList = analysisCache.get(currentUniqueDataFlag);
if (analysisCellList != null) {
return analysisCellList;
}
Sheet sheet = writeContext.writeSheetHolder().getCachedSheet();
Map<String, Set<Integer>> firstRowCache = new HashMap<String, Set<Integer>>(8);
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue;
}
for (int j = 0; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
if (cell == null) {
continue;
}
String preparedData = prepareData(cell, i, j, firstRowCache);
// Prevent empty data from not being replaced
if (preparedData != null) {
cell.setCellValue(preparedData);
}
}
}
return analysisCache.get(currentUniqueDataFlag);
}
I can't understand what I'm doing. Time does not allow, did not study hard.
I can't find relevant information in the GitHub issue. Put the upgraded version number of the dependency to 3.0.1
, and report an error:
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.http.ResponseEntity["body"]->org.springframework.core.io.InputStreamResource["inputStream"]->java.io.FileInputStream["fd"])
Add the following configuration:
static {
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
The error disappears, but the exported Excel is empty! ? ?
It is worth noting that:
when using EasyExcel version 2.2.11, based on the above EasyExcel
export tool class, there is no problem, and Excel files can be exported with data.
After upgrading to 3.0.1
a version, the export file is empty.
Export Excel file is empty
the problems mentioned above. I really can't figure it out, so raise an issue in the GitHub Issue that the export Excel is empty .
Workaround:
remove this line:writeSheet.setSheetName(fileName);
Me: Confused? ? ?