前言
EasyExcel 是阿里巴巴开源的一款 Excel 处理工具,EasyExcel 使用起来快速简便,性能高效。本文将基于日常开发中的业务场景来介绍 EasyExcel 的应用及代码实现。限于篇幅及文章主旨,本文不会对 EasyExcel 的基本 API 作讲解,因为可在官方文档查阅。
EasyExcel 官方文档:easyexcel.opensource.alibaba.com/docs/curren...
导出Excel
导出 Excel,就是将数据导出为 Excel 格式文件,也称为写入 Excel。它有以下应用场景:
- 导出 Excel 到本地
- Web 下载 Excel
- Excel 导出任务
导出Excel到本地
导出 Excel 到本地是最常用的场景,在业务中通常是根据给定的 Excel 模版导出数据,而数据则通过数据库读取。
ExcelUtil
中导出 Excel 到本地的方法,如下示例:
java
public class ExcelUtil {
/**
* 写入Excel
* @param pathName 路径
* @param head 表头
* @param data 数据
*/
public static void writeExcel(String pathName, Class<?> head, List<?> data) {
EasyExcel.write(pathName).head(head).sheet("Sheet1").doWrite(data);
}
}
Web下载Excel
Web 下载 Excel 是指实时地生成 Excel 并通过 Web 网络下载,同样地生成的 Excel 也是根据给定的模版要求导出特定数据;
实际上,Web 下载 Excel 会占用网络资源,业务量大时对服务器压力也不小,所以通常是以 Excel 导出任务的方式替代这样的业务场景。
ExcelUtil
中 Web 下载 Excel 的方法称为 downloadExcel
,如下示例:
java
public class ExcelUtil {
/**
* 下载Excel
* @param response HTTP Response
* @param head 表头
* @param data 数据
*/
public static void downloadExcel(HttpServletResponse response, Class<?> head, List<?> data) throws IOException {
try {
ExcelTypeEnum xlsx = ExcelTypeEnum.XLSX;
String fileName = URLEncoder.encode(FileUtil.randomName(xlsx.getValue()), "UTF-8").replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
EasyExcel.write(outputStream).head(head).autoCloseStream(autoCloseStream).sheet("Sheet1").doWrite(data);
} catch (IOException e) {
e.printStackTrace();
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().println(JSON.toJSONString(ApiResponse.result(ApiResult.WEB_DOWNLOAD_ERROR)));
}
}
}
Excel导出任务
Excel 导出任务,就是先创建需要的导出任务,等待调度服务完成 Excel 导出,之后再手动下载需要的 Excel;
调度服务完成 Excel 导出,这个过程实际上可导出到本地服务器,或上传到对象存储服务(如 OSS);
当需要上传到对象存储服务时,通过流的方式导出 Excel 并上传。
ExcelUtil
中流式导出 Excel 的方法如下示例:
java
public class ExcelUtil {
/**
* 写入Excel
* @param outputStream 流
* @param head 表头
* @param data 数据
* @param autoCloseStream 自动关闭流
*/
public static void writeExcel(OutputStream outputStream, Class<?> head, List<?> data, Boolean autoCloseStream) {
EasyExcel.write(outputStream).head(head).autoCloseStream(autoCloseStream).sheet("Sheet1").doWrite(data);
}
}
在完成流式导出 Excel 后,此时数据以OutputStream
形式存在,当需要上传至对象存储服务时,则要把OutputStream
转为InputStream
,伪代码如下:
java
// 假设这里是Excel数据
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 将OutputStream的内容转为InputStream
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
之后,就可以调用对象存储服务的上传方法,如下:
java
/**
* 阿里云OSS工具类
*/
public class AliOSSUtil {
public static void upload(String fileName, InputStream inputStream) {
OSS ossClient = new OSSClientBuilder().build(AliOSSConfig.ENDPOINT, AliOSSConfig.ACCESS_KEY_ID, AliOSSConfig.ACCESS_KEY_SECRET);
PutObjectResult putObjectResult = ossClient.putObject(AliOSSConfig.BUCKET_NAME, fileName, inputStream);
ossClient.shutdown();
}
}
导入Excel
导入 Excel,就是解析 Excel 文件数据 ,也称为读取 Excel,有以下场景:
- 从本地导入 Excel 文件
- Web 上传 Excel
从本地导入Excel文件
从本地导入 Excel 文件是比较的基础业务场景,通常是将本地 Excel 文件按照数据规则读取出来后入库保存;
更常见的业务场景,是Web上传Excel
。
Web上传Excel
Web 上传 Excel 是更为常见的业务场景,通常是在管理后台上传 Excel 文件,然后服务端根据规则读取数据,之后入库保存。
ExcelUtil
中读取 Excel 的方法如下示例:
java
public class ExcelUtil {
/**
* 读取Excel
* @param inputStream 流
* @param head 表头
* @return 数据
*/
public static <T> List<T> readExcel(InputStream inputStream, Class<?> head) {
ExcelListener<T> listener = new ExcelListener<>();
EasyExcel.read(inputStream, head, listener).sheet().doRead();
return listener.getRows();
}
}
在controller
层中,我们可以很方便地利用 Spring 的MultipartFile
来获取文件的数据流并调用EasyExcel
读取其中的数据,无需将数据流转为本地文件再处理,避免占用存储空间。
代码调用如下示例:
java
@Api(tags = "文件处理")
@RestController
@RequestMapping("/admin-service")
public class FileHandleController {
@ApiOperation("上传Excel")
@PostMapping("/uploadExcel")
public ApiResponse<ApiResult> uploadExcel(@RequestParam("file") MultipartFile file) {
try {
List<OrderHead> list = ExcelUtil.readExcel(file.getInputStream(), OrderHead.class);
// ......
} catch (IOException e) {
e.printStackTrace();
}
return ApiResponse.success();
}
}
导入Excel分批入库
在导入 Excel 的业务中,时常会处理大量数据的 Excel 表格;
然而在数据量大的时候一次性批量插入数据库会产生性能问题,所以通常是将大量数据分批次入库。
EasyExcel 中提供了一个PageReadListener
的监听器,可以用于分批读取。
ExcelUtil
中分批读取 Excel 的方法如下示例:
java
public class ExcelUtil {
/**
* 读取Excel
* 可以分批次读取,再入库保存
* @param inputStream 流
* @param head 表头
* @param listener 监听器
*/
public static <T> void readExcel(InputStream inputStream, Class<?> head, PageReadListener<T> listener) {
EasyExcel.read(inputStream, head, listener).sheet().doRead();
}
}
PageReadListener
PageReadListener
用于实现数据的分批处理,默认是每个批次100条数据,也可以在构造函数中指定;
PageReadListener
的构造函数中至少要传入一个Consumer
,也就是需要定义一个如何消费的函数;
在invoke
方法中(每读取一条都会回调),当缓存的数据量达到指定的阈值(BATCH_COUNT
),就执行Consumer
;
当所有数据都已读取完,则会调用doAfterAllAnalysed
方法,如果有存余的缓存数据,那么执行Consumer
。
PageReadListener
源码如下:
java
public class PageReadListener<T> implements ReadListener<T> {
public static int BATCH_COUNT = 100;
private List<T> cachedDataList;
private final Consumer<List<T>> consumer;
private final int batchCount;
public PageReadListener(Consumer<List<T>> consumer) {
this(consumer, BATCH_COUNT);
}
public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
this.consumer = consumer;
this.batchCount = batchCount;
}
public void invoke(T data, AnalysisContext context) {
this.cachedDataList.add(data);
if (this.cachedDataList.size() >= this.batchCount) {
this.consumer.accept(this.cachedDataList);
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(this.batchCount);
}
}
public void doAfterAllAnalysed(AnalysisContext context) {
if (CollectionUtils.isNotEmpty(this.cachedDataList)) {
this.consumer.accept(this.cachedDataList);
}
}
}
代码调用
在service
层中我们调用ExcelUtil#readExcel(InputStream inputStream, Class<?> head, PageReadListener<T> listener)
方法分批读取 Excel 数据,这里需要一个PageReadListener
;
而前面讲,PageReadListener
需要传入一个消费函数,来定义如何消费每个批次的数据,对于业务来讲就是将每个批次的数据执行入库操作,这里就是调用dao
层的代码。
代码调用如下示例:
java
/**
* Excel分批读取入库接口
* 可以实现分批次读取Excel数据再入库
*/
public interface ExcelService {
default <T> void saveData(InputStream inputStream, Class<?> head, int batchCount) {
PageReadListener<T> listener = new PageReadListener<T>(dataList -> {
System.out.println(dataList.toString());
// excelMapper.saveData(dataList);
}, batchCount);
ExcelUtil.readExcel(inputStream, head, listener);
}
}