使用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)
格式化
这里需要做一些对比,所以放一下数据模板的截图
我们可以看到,生日和生日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);
}
与最初的版本并没有什么区别,也就是说使用转换器只需要在数据接收类上加上自定义注解即可