EasyExcel实战与笔记

概述

Excel导入导出是业务开发中非常常见的需求。本文记录一下如何快速入门使用EasyExcel,深度实战,以及遇到的问题。

入门

使用EasyExcel导入如下依赖即可:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
</dependency>

使用EasyExcel主要是两类场景:

  1. 上传Excel文件并解析Sheet里数据,数据校验,脏数据清除,数据落库等;
  2. 从数据库里查询到数据后,下载导出为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>

基本上入门使用就是上面这些内容。

进阶

动态添加自增序号列

想要实现的效果:
在这里插入图片描述
实现方案,增加一个自定义的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) {
    
    
    }
}

注册此RowWriteHandler:

ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(inputStream).registerWriteHandler(new CustomRowWriteHandler()).build();

问题

记录几个在使用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)

使用的版本号为:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.11</version>
</dependency>

报错的源码如下:

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);
}

看不明白在干嘛。时间不允许,没有好好去钻研。

GitHub issue里找不到相关信息。把依赖的升级版本号到3.0.1,报错:

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"])

增加如下配置:

static {
    
    
	mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}

报错消失,但是导出的Excel为空!??

值得注意的是:
在使用EasyExcel 2.2.11版本时,基于上面的EasyExcel导出工具类,是没有问题的,可以导出Excel文件,且有数据。

升级到3.0.1版本后,导出文件为空。

导出Excel文件为空

上面提到的问题。实在是搞不定,在GitHub Issue里提个导出Excel为空的Issue。

解决方法:
删除这一行:writeSheet.setSheetName(fileName);

我:一脸懵逼???

参考

猜你喜欢

转载自blog.csdn.net/lonelymanontheway/article/details/131258245