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

打印 上一主题 下一主题

主题 866|帖子 866|积分 2598

作者:风雨兼程
来源:jianshu.com/p/8f3defdc76d4
EasyExcel

在做excel导入导出的时候,发现项目中封装的工具类及其难用,于是去gitHub上找了一些相关的框架,最终选定了EasyExcel。之前早有听闻该框架,但是一直没有去了解,这次借此学习一波,提高以后的工作效率。
实际使用中,发现是真的很easy,大部分api通过名称就能知道大致意思,这点做的很nice。参考文档,大部分场景的需求基本都能够满足。
GitHub上的官方说明


快速开始

maven仓库地址
  1. <dependency>
  2.     <groupId>com.alibaba</groupId>
  3.     <artifactId>easyexcel</artifactId>
  4.     <version>2.1.2</version>
  5. </dependency>
复制代码
推荐一个开源免费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
导入

如下图excel表格:


建立导入对应实体类
  1. @Data
  2. public class ReqCustomerDailyImport {
  3.     /**
  4.      * 客户名称
  5.      */
  6.     @ExcelProperty(index = 0)
  7.     private String customerName;
  8.     /**
  9.      * MIS编码
  10.      */
  11.     @ExcelProperty(index = 1)
  12.     private String misCode;
  13.     /**
  14.      * 月度滚动额
  15.      */
  16.     @ExcelProperty(index = 3)
  17.     private BigDecimal monthlyQuota;
  18.     /**
  19.      * 最新应收账款余额
  20.      */
  21.     @ExcelProperty(index = 4)
  22.     private BigDecimal accountReceivableQuota;
  23.     /**
  24.      * 本月利率(年化)
  25.      */
  26.     @ExcelProperty(index = 5)
  27.     private BigDecimal dailyInterestRate;
  28. }
复制代码
Controller代码
  1. @PostMapping("/import")
  2. public void importCustomerDaily(@RequestParam MultipartFile file) throws IOException {
  3.     InputStream inputStream = file.getInputStream();
  4.     List<ReqCustomerDailyImport> reqCustomerDailyImports = EasyExcel.read(inputStream)
  5.             .head(ReqCustomerDailyImport.class)
  6.             // 设置sheet,默认读取第一个
  7.             .sheet()
  8.             // 设置标题所在行数
  9.             .headRowNumber(2)
  10.             .doReadSync();
  11. }
复制代码
运行结果


可以看出只需要在实体对象使用@ExcelProperty注解,读取时指定该class,即可读取,并且自动过滤了空行,对于excel的读取及其简单。不过此时发现一个问题,这样我如果要校验字段该怎么办?要将字段类型转换成另外一个类型呢?
不必担心,我们可以想到的问题,作者肯定也考虑到了,下面来一个Demo
代码如下
  1. List<ReqCustomerDailyImport> reqCustomerDailyImports = EasyExcel.read(inputStream)
  2.             // 这个转换是成全局的, 所有java为string,excel为string的都会用这个转换器。
  3.             // 如果就想单个字段使用请使用@ExcelProperty 指定converter
  4.             .registerConverter(new StringConverter())
  5.             // 注册监听器,可以在这里校验字段
  6.             .registerReadListener(new CustomerDailyImportListener())
  7.             .head(ReqCustomerDailyImport.class)
  8.             .sheet()
  9.             .headRowNumber(2)
  10.             .doReadSync();
  11. }
复制代码
监听器
  1. public class CustomerDailyImportListener extends AnalysisEventListener {
  2.     List misCodes = Lists.newArrayList();
  3.     /**
  4.      * 每解析一行,回调该方法
  5.      * @param data
  6.      * @param context
  7.      */
  8.     @Override
  9.     public void invoke(Object data, AnalysisContext context) {
  10.         String misCode = ((ReqCustomerDailyImport) data).getMisCode();
  11.         if (StringUtils.isEmpty(misCode)) {
  12.             throw new RuntimeException(String.format("第%s行MIS编码为空,请核实", context.readRowHolder().getRowIndex() + 1));
  13.         }
  14.         if (misCodes.contains(misCodes)) {
  15.             throw new RuntimeException(String.format("第%s行MIS编码已重复,请核实", context.readRowHolder().getRowIndex() + 1));
  16.         } else {
  17.             misCodes.add(misCode);
  18.         }
  19.     }
  20.     /**
  21.      * 出现异常回调
  22.      * @param exception
  23.      * @param context
  24.      * @throws Exception
  25.      */
  26.     @Override
  27.     public void onException(Exception exception, AnalysisContext context) throws Exception {
  28.         // ExcelDataConvertException:当数据转换异常的时候,会抛出该异常,此处可以得知第几行,第几列的数据
  29.         if (exception instanceof ExcelDataConvertException) {
  30.             Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
  31.             Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
  32.             String message = "第" + rowIndex + "行,第" + columnIndex + "列" + "数据格式有误,请核实";
  33.             throw new RuntimeException(message);
  34.         } else if (exception instanceof RuntimeException) {
  35.             throw exception;
  36.         } else {
  37.             super.onException(exception, context);
  38.         }
  39.     }
  40.     /**
  41.      * 解析完全部回调
  42.      * @param context
  43.      */
  44.     @Override
  45.     public void doAfterAllAnalysed(AnalysisContext context) {
  46.         misCodes.clear();
  47.     }
  48. }
复制代码
转换器
  1. public class StringConverter implements Converter<String> {
  2.     @Override
  3.     public Class supportJavaTypeKey() {
  4.         return String.class;
  5.     }
  6.     @Override
  7.     public CellDataTypeEnum supportExcelTypeKey() {
  8.         return CellDataTypeEnum.STRING;
  9.     }
  10.     /**
  11.      * 将excel对象转成Java对象,这里读的时候会调用
  12.      *
  13.      * @param cellData            NotNull
  14.      * @param contentProperty     Nullable
  15.      * @param globalConfiguration NotNull
  16.      * @return
  17.      */
  18.     @Override
  19.     public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
  20.                                     GlobalConfiguration globalConfiguration) {
  21.         return "自定义:" + cellData.getStringValue();
  22.     }
  23.     /**
  24.      * 将Java对象转成String对象,写出的时候调用
  25.      *
  26.      * @param value
  27.      * @param contentProperty
  28.      * @param globalConfiguration
  29.      * @return
  30.      */
  31.     @Override
  32.     public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
  33.                                        GlobalConfiguration globalConfiguration) {
  34.         return new CellData(value);
  35.     }
  36. }
复制代码
可以看出注册了一个监听器: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不支持根据名字去匹配。
导出

建立导出对应实体类
  1. @Data
  2. @Builder
  3. public class RespCustomerDailyImport {
  4.     @ExcelProperty("客户编码")
  5.     private String customerName;
  6.     @ExcelProperty("MIS编码")
  7.     private String misCode;
  8.     @ExcelProperty("月度滚动额")
  9.     private BigDecimal monthlyQuota;
  10.     @ExcelProperty("最新应收账款余额")
  11.     private BigDecimal accountReceivableQuota;
  12.     @NumberFormat("#.##%")
  13.     @ExcelProperty("本月利率(年化)")
  14.     private BigDecimal dailyInterestRate;
  15. }
复制代码
Controller代码
  1. @GetMapping("/export")
  2. public void export(HttpServletResponse response) throws IOException {
  3.     // 生成数据
  4.     List<RespCustomerDailyImport> respCustomerDailyImports = Lists.newArrayList();
  5.     for (int i = 0; i < 50; i++) {
  6.         RespCustomerDailyImport respCustomerDailyImport = RespCustomerDailyImport.builder()
  7.                 .misCode(String.valueOf(i))
  8.                 .customerName("customerName" + i)
  9.                 .monthlyQuota(new BigDecimal(String.valueOf(i)))
  10.                 .accountReceivableQuota(new BigDecimal(String.valueOf(i)))
  11.                 .dailyInterestRate(new BigDecimal(String.valueOf(i))).build();
  12.         respCustomerDailyImports.add(respCustomerDailyImport);
  13.     }
  14.    
  15.     response.setContentType("application/vnd.ms-excel");
  16.     response.setCharacterEncoding("utf-8");
  17.     // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
  18.     String fileName = URLEncoder.encode("导出", "UTF-8");
  19.     response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
  20.     EasyExcel.write(response.getOutputStream(), RespCustomerDailyImport.class)
  21.             .sheet("sheet0")
  22.             // 设置字段宽度为自动调整,不太精确
  23.             .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
  24.             .doWrite(respCustomerDailyImports);
  25. }
复制代码
导出效果


导出相关常用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开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表