Java EasyExcel

EasyExcel快速开始

EasyExcel官方文档

https://www.yuque.com/easyexcel/doc/easyexcel

导入EasyExcel相关的依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>easyexcel</artifactId>
  4. <version>2.1.2</version>
  5. </dependency>

导入

简单导入的示例

建立导入对应实体类

  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的读取及其简单。不过此时发现一个问题,

导入时校验字段,字段类型转换

  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中的那一列。可以根据名字或者Iindex去匹配。当然也可以不写,默认第一个字段就是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 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。
  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用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. response.setContentType("application/vnd.ms-excel");
    15. response.setCharacterEncoding("utf-8");
    16. // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    17. String fileName = URLEncoder.encode("导出", "UTF-8");
    18. response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    19. EasyExcel.write(response.getOutputStream(), RespCustomerDailyImport.class)
    20. .sheet("sheet0")
    21. // 设置字段宽度为自动调整,不太精确
    22. .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
    23. .doWrite(respCustomerDailyImports);
    24. }

    导出相关常用API

    注解

  • ExcelProperty 指定写到第几列,默认根据成员变量排序。value指定写入的名称,默认成员变量的名字。

  • ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段。
  • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat
  • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

    EasyExcel相关参数

  • needHead 监听器是否导出头。

  • useDefaultStyle 写的时候是否是使用默认头。
  • headclazz二选一。写入文件的头列表,建议使用class
  • autoTrim 字符串、表头等数据自动trim
  • sheetNo 需要写入的编码。默认0。
  • sheetName 需要些的Sheet名称,默认同sheetNo。

    总结

    可以看出不管是excel的读取还是写入,都是一个注解加上一行代码完成,可以少很多解析的代码,极大减少了重复的工作量。EasyExcel还支持更多场景,例如读,可以读多个sheet,也可以解析一行数据或者多行数据做一次入库操作;写操作中,支持复杂头,指定列写入,重复多次写入,多个sheet写入,根据模板写入等等。更多的例子可以参考EasyExcel官方文档。