自从用了 EasyExcel,导入导出 Excel 更简单了!

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 自从用了 EasyExcel,导入导出 Excel 更简单了!

作者:风雨兼程

来源: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表格:


image.png



建立导入对应实体类

@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方法。因此,可以在监听器中校验数据,在转换器中转换数据类型或者格式。


运行结果

image.png


修改一下表格,测试校验是否生效


image.png


再次导入,查看运行结果


image.png


导入相关常用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);
}


导出效果

image.png


导出相关常用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月前
|
easyexcel Java 关系型数据库
阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel
该文章主要介绍了在Java应用中如何使用EasyExcel技术完成对Excel文件的导入和导出操作,包括环境搭建、基本概念、快速入门、进阶操作和综合应用等内容,并提供了相关代码示例和注意事项。
 阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel
|
1月前
|
JavaScript 前端开发 easyexcel
基于SpringBoot + EasyExcel + Vue + Blob实现导出Excel文件的前后端完整过程
本文展示了基于SpringBoot + EasyExcel + Vue + Blob实现导出Excel文件的完整过程,包括后端使用EasyExcel生成Excel文件流,前端通过Blob对象接收并触发下载的操作步骤和代码示例。
218 0
基于SpringBoot + EasyExcel + Vue + Blob实现导出Excel文件的前后端完整过程
|
1月前
|
开发框架 算法 .NET
C#使用MiniExcel导入导出数据到Excel/CSV文件
C#使用MiniExcel导入导出数据到Excel/CSV文件
44 0
|
3月前
|
Java easyexcel
java开发excel导入导出工具类基于EasyExcel
java开发excel导入导出工具类基于EasyExcel
182 1
|
3月前
|
前端开发 Java 开发工具
如何在Spring Boot框架下实现高效的Excel服务端导入导出?
ArtifactId:是项目的唯一标识符,在实际开发中一般对应项目的名称,就是项目根目录的名称。 Group Id,Artfact Id是保证项目唯一性的标识,一般来说如果项目打包上传至maven这样的包管理仓库中。在搜索你的项目时,Group Id,Artfact Id是必要的条件。 Version:版本号,默认0.0.1-SNAPSHOT。SNAPSHOT代表不稳定的版本,与之相对的有RELEASE。 Project type:工程的类型,maven工程还是gradle工程。 Language:语言(Java,Kotlin,Groovy)。
|
3月前
|
easyexcel Java API
Apache POI与easyExcel:Excel文件导入导出的技术深度分析
Apache POI与easyExcel:Excel文件导入导出的技术深度分析
|
1月前
|
关系型数据库 MySQL Shell
不通过navicat工具怎么把查询数据导出到excel表中
不通过navicat工具怎么把查询数据导出到excel表中
31 0
|
18天前
|
数据采集 存储 数据挖掘
使用Python读取Excel数据
本文介绍了如何使用Python的`pandas`库读取和操作Excel文件。首先,需要安装`pandas`和`openpyxl`库。接着,通过`read_excel`函数读取Excel数据,并展示了读取特定工作表、查看数据以及计算平均值等操作。此外,还介绍了选择特定列、筛选数据和数据清洗等常用操作。`pandas`是一个强大且易用的工具,适用于日常数据处理工作。
|
1月前
|
SQL JSON 关系型数据库
n种方式教你用python读写excel等数据文件
n种方式教你用python读写excel等数据文件
|
1月前
|
存储 Java Apache