⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 jianshu.com/p/8f3defdc76d4 「风雨兼程」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

EasyExcel

在做excel导入导出的时候,发现项目中封装的工具类及其难用,于是去gitHub上找了一些相关的框架,最终选定了EasyExcel。之前早有听闻该框架,但是一直没有去了解,这次借此学习一波,提高以后的工作效率。

实际使用中,发现是真的很easy,大部分api通过名称就能知道大致意思,这点做的很nice。参考文档,大部分场景的需求基本都能够满足。

GitHub上的官方说明

图片

快速开始

maven仓库地址

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

导入

如下图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写入,根据模板写入等等。更多的例子可以去参考EasyExcel官方文档

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

文章目录
  1. 1. EasyExcel
    1. 1.1. GitHub上的官方说明
  2. 2. 快速开始
    1. 2.1. maven仓库地址
    2. 2.2. 导入
      1. 2.2.1. 如下图excel表格:
      2. 2.2.2. 建立导入对应实体类
      3. 2.2.3. Controller代码
      4. 2.2.4. 运行结果
      5. 2.2.5. 代码如下
      6. 2.2.6. 监听器
      7. 2.2.7. 转换器
      8. 2.2.8. 运行结果
      9. 2.2.9. 导入相关常用API
        1. 2.2.9.1. 注解
        2. 2.2.9.2. EasyExcel相关参数
    3. 2.3. 导出
      1. 2.3.1. 建立导出对应实体类
      2. 2.3.2. Controller代码
      3. 2.3.3. 导出效果
      4. 2.3.4. 导出相关常用API
        1. 2.3.4.1. 注解
        2. 2.3.4.2. EasyExcel相关参数
  3. 3. 总结