作者:风雨兼程 \
起源: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 表格:
建设导入对应实体类
@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 办法。因而,能够在监听器中校验数据,在转换器中转换数据类型或者格局。
运行后果
批改一下表格,测试校验是否失效
再次导入,查看运行后果
导入相干罕用 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);
}
导出成果
导出相干罕用 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 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!