关于java:自从用了-EasyExcel导入导出-Excel-更简单了

3次阅读

共计 6778 个字符,预计需要花费 17 分钟才能阅读完成。

作者:风雨兼程 \
起源:jianshu.com/p/8f3defdc76d4

EasyExcel

在做 excel 导入导出的时候, 发现我的项目中封装的工具类及其难用, 于是去 gitHub 上找了一些相干的框架, 最终选定了 EasyExcel。之前早有听闻该框架,然而始终没有去理解,这次借此学习一波,进步当前的工作效率。

理论应用中,发现是真的很 easy,大部分 api 通过名称就能晓得大抵意思,这点做的很 nice。参考文档,大部分场景的需要根本都可能满足。

GitHub 上的官网阐明

疾速开始

maven 仓库地址

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

举荐一个开源收费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

导入

如下图 excel 表格:

建设导入对应实体类

@Data
public class ReqCustomerDailyImport {
    /**
     * 客户名称
     */
    @ExcelProperty(index = 0)
    private String customerName;

    /**
     * MIS 编码
     */
    @ExcelProperty(index = 1)
    private String misCode;

    /**
     * 月度滚动额
     */
    @ExcelProperty(index = 3)
    private BigDecimal monthlyQuota;

    /**
     * 最新应收账款余额
     */
    @ExcelProperty(index = 4)
    private BigDecimal accountReceivableQuota;

    /**
     * 本月利率 ( 年化)*/
    @ExcelProperty(index = 5)
    private BigDecimal dailyInterestRate;
}

Controller 代码

@PostMapping("/import")
public void importCustomerDaily(@RequestParam MultipartFile file) throws IOException {InputStream inputStream = file.getInputStream();
    List<ReqCustomerDailyImport> reqCustomerDailyImports = EasyExcel.read(inputStream)
            .head(ReqCustomerDailyImport.class)
            // 设置 sheet, 默认读取第一个
            .sheet()
            // 设置题目所在行数
            .headRowNumber(2)
            .doReadSync();}

运行后果

能够看出只须要在实体对象应用 @ExcelProperty 注解,读取时指定该 class,即可读取,并且主动过滤了空行,对于 excel 的读取及其简略。不过此时发现一个问题,这样我如果要校验字段该怎么办?要将字段类型转换成另外一个类型呢?

不用放心,咱们能够想到的问题,作者必定也思考到了,上面来一个 Demo

代码如下

List<ReqCustomerDailyImport> reqCustomerDailyImports = EasyExcel.read(inputStream)
            // 这个转换是成全局的,所有 java 为 string,excel 为 string 的都会用这个转换器。// 如果就想单个字段应用请应用 @ExcelProperty 指定 converter
            .registerConverter(new StringConverter())
            // 注册监听器,能够在这里校验字段
            .registerReadListener(new CustomerDailyImportListener())
            .head(ReqCustomerDailyImport.class)
            .sheet()
            .headRowNumber(2)
            .doReadSync();}

监听器

public class CustomerDailyImportListener extends AnalysisEventListener {List misCodes = Lists.newArrayList();

    /**
     * 每解析一行,回调该办法
     * @param data
     * @param context
     */
    @Override
    public void invoke(Object data, AnalysisContext context) {String misCode = ((ReqCustomerDailyImport) data).getMisCode();
        if (StringUtils.isEmpty(misCode)) {throw new RuntimeException(String.format("第 %s 行 MIS 编码为空,请核实", context.readRowHolder().getRowIndex() + 1));
        }
        if (misCodes.contains(misCodes)) {throw new RuntimeException(String.format("第 %s 行 MIS 编码已反复,请核实", context.readRowHolder().getRowIndex() + 1));
        } else {misCodes.add(misCode);
        }
    }

    /**
     * 出现异常回调
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        // ExcelDataConvertException: 当数据转换异样的时候,会抛出该异样,此处能够得悉第几行,第几列的数据
        if (exception instanceof ExcelDataConvertException) {Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
            Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
            String message = "第" + rowIndex + "行,第" + columnIndex + "列" + "数据格式有误,请核实";
            throw new RuntimeException(message);
        } else if (exception instanceof RuntimeException) {throw exception;} else {super.onException(exception, context);
        }
    }

    /**
     * 解析齐全部回调
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {misCodes.clear();
    }
}

转换器

public class StringConverter implements Converter<String> {

    @Override
    public Class supportJavaTypeKey() {return String.class;}

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}

    /**
     * 将 excel 对象转成 Java 对象,这里读的时候会调用
     *
     * @param cellData            NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
                                    GlobalConfiguration globalConfiguration) {return "自定义:" + cellData.getStringValue();
    }

    /**
     * 将 Java 对象转成 String 对象,写出的时候调用
     *
     * @param value
     * @param contentProperty
     * @param globalConfiguration
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
                                       GlobalConfiguration globalConfiguration) {return new CellData(value);
    }
}

能够看出注册了一个监听器:CustomerDailyImportListener, 还注册了一个转换器:StringConverter。流程为:框架读取一行数据,先执行转换器,当一行数据转换实现,执行监听器的回调办法,如果转换的过程中,呈现转换异样,也会回调监听器中的 onException 办法。因而,能够在监听器中校验数据,在转换器中转换数据类型或者格局。

运行后果

批改一下表格,测试校验是否失效

再次导入,查看运行后果

导入相干罕用 API

注解
  • ExcelProperty 指定以后字段对应 excel 中的那一列。能够依据名字或者 Index 去匹配。当然也能够不写,默认第一个字段就是 index=0,以此类推。千万留神,要么全副不写,要么全副用 index,要么全副用名字去匹配。千万别三个混着用,除非你十分理解源代码中三个混着用怎么去排序的。
  • ExcelIgnore 默认所有字段都会和 excel 去匹配,加了这个注解会疏忽该字段。
  • DateTimeFormat 日期转换,用 String 去接管 excel 日期格局的数据会调用这个注解。外面的 value 参照 java.text.SimpleDateFormat。
  • NumberFormat 数字转换,用 String 去接管 excel 数字格局的数据会调用这个注解。外面的 value 参照 java.text.DecimalFormat。
EasyExcel 相干参数
  • readListener 监听器,在读取数据的过程中会一直的调用监听器。
  • converter 转换器,默认加载了很多转换器。也能够自定义,如果应用的是 registerConverter,那么该转换器是全局的,如果要对单个字段失效,能够在 ExcelProperty 注解的 converter 指定转换器。
  • headRowNumber 须要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。
  • head 与 clazz 二选一。读取文件头对应的列表,会依据列表匹配数据,倡议应用 class。
  • autoTrim 字符串、表头等数据主动 trim。
  • sheetNo 须要读取 Sheet 的编码,倡议应用这个来指定读取哪个 Sheet。
  • sheetName 依据名字去匹配 Sheet,excel 2003 不反对依据名字去匹配。

导出

建设导出对应实体类

@Data
@Builder
public class RespCustomerDailyImport {@ExcelProperty("客户编码")
    private String customerName;

    @ExcelProperty("MIS 编码")
    private String misCode;

    @ExcelProperty("月度滚动额")
    private BigDecimal monthlyQuota;

    @ExcelProperty("最新应收账款余额")
    private BigDecimal accountReceivableQuota;

    @NumberFormat("#.##%")
    @ExcelProperty("本月利率 ( 年化)")
    private BigDecimal dailyInterestRate;
}

Controller 代码

@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
    // 生成数据
    List<RespCustomerDailyImport> respCustomerDailyImports = Lists.newArrayList();
    for (int i = 0; i < 50; i++) {RespCustomerDailyImport respCustomerDailyImport = RespCustomerDailyImport.builder()
                .misCode(String.valueOf(i))
                .customerName("customerName" + i)
                .monthlyQuota(new BigDecimal(String.valueOf(i)))
                .accountReceivableQuota(new BigDecimal(String.valueOf(i)))
                .dailyInterestRate(new BigDecimal(String.valueOf(i))).build();
        respCustomerDailyImports.add(respCustomerDailyImport);
    }
    
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    // 这里 URLEncoder.encode 能够避免中文乱码 当然和 easyexcel 没有关系
    String fileName = URLEncoder.encode("导出", "UTF-8");
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(), RespCustomerDailyImport.class)
            .sheet("sheet0")
            // 设置字段宽度为主动调整,不太准确
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .doWrite(respCustomerDailyImports);
}

导出成果

导出相干罕用 API

注解
  • ExcelProperty 指定写到第几列,默认依据成员变量排序。value 指定写入的名称,默认成员变量的名字。
  • ExcelIgnore 默认所有字段都会写入 excel,这个注解会疏忽这个字段。
  • DateTimeFormat 日期转换,将 Date 写到 excel 会调用这个注解。外面的 value 参照 java.text.SimpleDateFormat。
  • NumberFormat 数字转换,用 Number 写 excel 会调用这个注解。外面的 value 参照 java.text.DecimalFormat。
EasyExcel 相干参数
  • needHead 监听器是否导出头。
  • useDefaultStyle 写的时候是否是应用默认头。
  • head 与 clazz 二选一。写入文件的头列表,倡议应用 class。
  • autoTrim 字符串、表头等数据主动 trim。
  • sheetNo 须要写入的编码。默认 0。
  • sheetName 须要些的 Sheet 名称,默认同 sheetNo。

总结

能够看出不论是 excel 的读取还是写入,都是一个注解加上一行代码实现,能够让咱们少些很多解析的代码,极大缩小了反复的工作量。当然这两个例子应用了最简略的形式,EasyExcel 还反对更多场景,例如读,能够读多个 sheet,也能够解析一行数据或者多行数据做一次入库操作;写的话,反对简单头,指定列写入,反复屡次写入,多个 sheet 写入,依据模板写入等等。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿 (2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0