EasyExcel常用业务场景及代码实现

前言

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);
    }
}
相关推荐
cg50178 小时前
Spring Boot 的配置文件
java·linux·spring boot
橘猫云计算机设计11 小时前
基于springboot的考研成绩查询系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·python·考研·django·毕业设计
有一只柴犬12 小时前
深入Spring AI:6大核心概念带你入门AI开发
spring boot·后端
向阳25612 小时前
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
java·vue.js·spring boot·后端·sa-token·springboot·登录流程
XiaoLeisj12 小时前
【MyBatis】深入解析 MyBatis XML 开发:增删改查操作和方法命名规范、@Param 重命名参数、XML 返回自增主键方法
xml·java·数据库·spring boot·sql·intellij-idea·mybatis
风象南12 小时前
SpringBoot实现数据库读写分离的3种方案
java·spring boot·后端
CryptoPP13 小时前
springboot 对接马来西亚数据源API等多个国家的数据源
spring boot·后端·python·金融·区块链
清风絮柳13 小时前
52.个人健康管理系统小程序(基于springboot&vue)
vue.js·spring boot·毕业设计·前后端分离·健康管理系统·个人健康管理系统·个人健康管理小程序
forestsea13 小时前
使用 Spring Boot 和 GraalVM 的原生镜像
java·spring boot·spring native·原生映像
爱的叹息14 小时前
Spring Boot 集成Redis 的Lua脚本详解
spring boot·redis·lua