使用easyExcel进行简单的导入(附:源码)

  • 1.基本的导入(对应我的excelimport模块儿)
  • 用于接收xlsx数据的模板
  • 测试类
  • 2.easyExcel监听器(对应我的excellistener模块儿)
  • 添加一个自定义注解
  • 实现AnalysisEventListener接口
  • 在导入的方法中添加刚刚自定义的AnalysisEventListener的实现类
  • 3.easyExcel转换器与格式化(对应我的excelconverter)
  • 格式化
  • 转换器


废话不多说直接上代码

首先在pom文件中添加依赖,注意这里对应poi的版本是4.1.2

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

1.基本的导入(对应我的excelimport模块儿)

用于接收xlsx数据的模板

@Data
public class DemoOneModel {

    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty("年龄")
    private Integer age;

    @ExcelProperty("生日")
    private String birthday;

}

测试类

@SpringBootTest
class EasyexceldemoApplicationTests {

    private static String FILE_NAME = "D:\\project\\easyexceldemo\\excelimport\\src\\main\\resources\\测试文档张1.xlsx";

    @Test
    void contextLoads() throws IOException {
        //首先获取文件
        File file = new File(FILE_NAME);
        //把上传的文件转换成类
        List<DemoOneModel> models = EasyExcel.read(file)
                //excel标题对应实体类,实体类要用@ExcelProperty与标题一致
                .head(DemoOneModel.class)
                //设置sheet,默认读取页数,也可以是sheetName
                .sheet(0)
                //设置标题所在行数
                .headRowNumber(1)
                .doReadSync();
        System.out.println(models);
    }
}

控制台打印如下数据,可以看到数据成功导入

DemoOneModel(name=张1, age=12, birthday=1991/3/1), DemoOneModel(name=张2, age=13, birthday=1991/3/2), DemoOneModel(name=张3, age=14, birthday=1991/3/3)...

2.easyExcel监听器(对应我的excellistener模块儿)

在实际的开发中,我们有可能需要对导入的数据进行校验,或者别的处理,这时候我们可以这么做

@Test
 void noAnnoValid() throws IOException {
      //首先获取文件流
      File file = new File(FILE_NAME);
      //把上传的文件转换成类
      List<DemoOneModel> models = EasyExcel.read(file)
              //excel标题对应实体类,实体类要用@ExcelProperty与标题一致
              .head(DemoOneModel.class)
              //设置sheet,默认读取页数,也可以是sheetName
              .sheet(0)
              //设置标题所在行数
              .headRowNumber(1)
              .doReadSync();
      models.forEach(item -> {
          if (StringUtils.isBlank(item.getName())){
              throw new RuntimeException("姓名不能为空");
          }
          // handle......
          if (item.getAge() != null){
              throw new RuntimeException("年龄不能为空");
          }
          // handle......
          if (StringUtils.isBlank(item.getBirthday())){
              throw new RuntimeException("生日不能为空");
          }
          // handle......
      });
  }

虽然这样做完全可以满足我们的需求,但是我们可以看到,上面的代码的判空校验看起来非常痛苦,所以我们也可以像下面这样,通过AnalysisEventListener来让我们不用在业务逻辑里面看到这么多的校验数据的代码

添加一个自定义注解

@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelEmptyValid {

    String message() default "导入有未填入的字段";

}

在接收导入进来的参数的模板类上的字段上添加这个注解,用于判定哪个字段需要进行校验,就像这样

@Data
public class DemoOneModelWithAnnotation {

    @ExcelProperty("姓名")
    @ExcelEmptyValid(message = "姓名不能为空")
    private String name;

    @ExcelProperty("年龄")
    @ExcelEmptyValid(message = "年龄不能为空")
    private Integer age;

    @ExcelProperty("生日")
    @ExcelEmptyValid(message = "生日不能为空")
    private String birthday;

}

实现AnalysisEventListener接口

public class ExcelListener<T> extends AnalysisEventListener<DemoOneModelWithAnnotation> {
    
    @Override
    public void invoke(DemoOneModelWithAnnotation demoOneModel, AnalysisContext analysisContext) {
        //获取行号
        Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
        //处理拿到的数据
        ExcelImportValid.preHandle(demoOneModel,rowIndex);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//        analysisContext.
    }
}

在invoke方法中对数据进行了一个遍历,我们可以通过这个方法拿到每一行的数据然后进行处理。
在valid方法中,我们先是对每一个属性进行遍历,然后判断这个属性上有没有我们刚才写的自定义注解,如果有就对这个属性进行判空的操作

public class ExcelImportValid {

    /**
     * Excel导入字段校验
     *
     * @param object 校验的JavaBean 其属性须有自定义注解
     */
    public static void valid(Object object,Integer rowIndex)  {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            //设置可访问
            field.setAccessible(true);
            //属性的值
            Object fieldValue;
            try {
                fieldValue = field.get(object);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("获取参数失败失败");
            }
            //是否包含必填校验注解
            boolean isExcelValid = field.isAnnotationPresent(ExcelEmptyValid.class);
            if (isExcelValid && Objects.isNull(fieldValue)) {
                throw new RuntimeException("第" + rowIndex + "行" + field.getAnnotation(ExcelEmptyValid.class).message());
            }
        }
    }

    public static void preHandle(Object object,Integer rowIndex){
        valid(object,rowIndex);
    }
}

在导入的方法中添加刚刚自定义的AnalysisEventListener的实现类

这里与上面的区别就是在read方法中添加了ExcelListener

/**
     * 使用注解做校验
     */
    @Test
    void withAnnoValid() {
        //首先获取文件流
        File file = new File(FILE_NAME);
//        FileInputStream fileInputStream = new FileInputStream(FILE_NAME);
        //把上传的文件转换成类
        List<DemoOneModelWithAnnotation> models = EasyExcel.read(file,new ExcelListener<DemoOneModelWithAnnotation>())
                //excel标题对应实体类,实体类要用@ExcelProperty与标题一致
                .head(DemoOneModelWithAnnotation.class)
                //设置sheet,默认读取页数,也可以是sheetName
                .sheet(0)
                //设置标题所在行数
                .headRowNumber(1)
                .doReadSync();

    }

3.easyExcel转换器与格式化(对应我的excelconverter)

格式化

这里需要做一些对比,所以放一下数据模板的截图

easyescel 模板路径 easyexcel模板导入_开发语言


我们可以看到,生日和生日2,圆周率和圆周率2是一样的数据

然后我们再来看一下用于接收导入数据的类

@Data
public class DemoOneModel {

    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty("年龄")
    private Integer age;

    @ExcelProperty(value = "生日",converter = DateConverter.class)
    private Date birthday;

    @ExcelProperty(value = "生日2")
    @DateTimeFormat("yyyy-MM")
    private String birthdayTwo;

    @ExcelProperty(value = "是否在读",converter = IsConverter.class)
    private Integer graduate;

    @ExcelProperty(value = "圆周率")
    @NumberFormat("#.#")
    private Double pi;

    @ExcelProperty(value = "圆周率2")
    @NumberFormat("#.#")
    private String pi2;

}

在这里我们首先说一下NumberFormat以及DateTimeFormat这两个转换器。
这两个转换器都是easyExcel提供的,引包的时候记得引

import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;

然后我们看一下导入结果

name=张1, age=12, birthday=Fri Mar 01 00:00:00 CST 1991, birthdayTwo=1991-03, graduate=1, pi=3.1415, pi2=3.1

我们可以看到,通过DateConverter转成日期的结果跟导入的数据一样,birthdayTwo被DateTimeFormat格式化成了yyyy-MM的格式,浮点数pi的NumberFormat没有生效,而pi2的NumberFormat格式化生效了。
我们得出结论,DateTimeFormat以及NumberFormat只在String类型上生效

转换器

上面DemoOneModel 类中我们看到了两个我自定义的数据转换器分别是DateConverter,以及IsConverter。
easyExcel为我们提供了转化器接口Converter,只要实现这个接口就可以自定义转换器处理数据。

public class DateConverter implements Converter<Date> {

    @Override
    public Date convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        BigDecimal numberValue = cellData.getNumberValue();
        long second = numberValue.multiply(new BigDecimal("86400")).longValue();
        Instant instant = Instant.ofEpochSecond(second - 2209190400L);
        Date from = Date.from(instant);
        System.out.println(DateFormatUtils.format(from,"yyyy-MM-dd"));
        return from;
    }

    @Override
    public WriteCellData<String> convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return new WriteCellData<>(DateFormatUtils.format(value,"yyyy-MM-dd HH:mm:ss"));
    }

    @Override
    public Class<?> supportJavaTypeKey() {
        return Date.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
}
public class IsConverter implements Converter<Integer> {
    
    @Override
    public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        String data = cellData.getStringValue();
        if (data != null) {
            if (Objects.equals(data,"是")) {
                return 1;
            } else if (Objects.equals(data,"否")){
                return 0;
            }
        }
        return null;
    }
    //导出数据到excel
    @Override
    public WriteCellData<Integer> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return new WriteCellData<>(value == 1 ? "是" : "否");
    }

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

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

我们看到在读数据的时候,数据转换的逻辑都是写在convertToJavaData当中的。
最后再来看一下数据接收的方法

@Test
void contextLoads() {
     //首先获取文件流
     File file = new File(FILE_NAME);
     //把上传的文件转换成类
     List<DemoOneModel> models = EasyExcel.read(file)
             //excel标题对应实体类,实体类要用@ExcelProperty与标题一致
             .head(DemoOneModel.class)
             //设置sheet,默认读取页数,也可以是sheetName
             .sheet(0)
             //设置标题所在行数
             .headRowNumber(1)
             .doReadSync();
     System.out.println(models);
 }

与最初的版本并没有什么区别,也就是说使用转换器只需要在数据接收类上加上自定义注解即可