作者:风雨兼程 \
起源: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表格:
建设导入对应实体类
@Datapublic 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@Builderpublic 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开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!