Java开发常用工具-01-EasyExcel

内容分享2小时前发布 quettla
0 0 0

1-EasyExcel

EasyExcel的官网:
https://easyexcel.opensource.alibaba.com/

EasyExcel 是阿里巴巴开源的 Excel 读写框架,相比传统 Excel 工具(如 POI 原生 API、JXL 等),它的核心优势直击业务痛点,也是它被广泛使用的根本缘由:

  1. 极致节省内存,杜绝 OOM(内存溢出)异常这是 EasyExcel 的核心竞争力。传统 POI 采用「DOM 模式」,会将整个 Excel 文件的所有数据(单元格、样式、公式等)一次性加载到 JVM 内存中,当处理大文件(如 10 万行、100 万行数据)时,极易触发OutOfMemoryError,导致应用崩溃。而 EasyExcel 采用「SAX 流式解析模式」,逐行读取 / 写入 Excel 数据,不缓存全部数据到内存,仅保留当前行的解析上下文,即使是 GB 级别的 Excel 文件,也能平稳处理,内存占用始终保持在较低水平。
  2. 易用性极强,大幅提升开发效率EasyExcel 采用「注解驱动」设计,无需编写复杂的底层解析逻辑(如 POI 需要手动创建工作簿、工作表、遍历单元格、处理数据类型转换等)。仅通过@ExcelProperty等简单注解,即可快速实现 Excel 表头与 Java 实体类字段的映射,开箱即用,大幅减少模板化代码,降低学习成本和开发工作量(对应你之前通过实体类BigDataTableAssetsInfoExcelVo快速接收导入数据的场景)。
  3. 功能全面,覆盖绝大多数业务场景它并非轻量功能的简化框架,而是覆盖了 Excel 读写的全场景需求,无需额外集成其他工具:
    1. 支持 Excel 2003(.xls)和 Excel 2007+(.xlsx)两种格式,兼容性拉满;
    2. 支持复杂表头(合并单元格、多级表头)、自定义单元格样式(行高、列宽、字体、颜色);
    3. 支持特殊数据类型转换(日期、枚举、数字格式等)
    4. 支持导入数据校验、导出数据批量填充、模板导出等实用功能;
    5. 支持同步 / 异步读写,适配高并发场景。
  1. 兼容性良好,适配主流技术栈完美兼容 JDK 8+(包括你使用的 JDK 11),可无缝集成 Spring Boot、Spring Cloud 等主流 Java 框架,无额外适配成本;依赖简洁,不会引入大量冗余 JAR 包,减少项目依赖冲突风险。
  2. 兼容性良好,适配主流技术栈完美兼容 JDK 8+(包括你使用的 JDK 11),可无缝集成 Spring Boot、Spring Cloud 等主流 Java 框架,无额外适配成本;依赖简洁,不会引入大量冗余 JAR 包,减少项目依赖冲突风险。
  3. 官方维护有保障,社区活跃度高作为阿里巴巴开源项目,EasyExcel 有专业团队持续维护更新,及时修复 BUG、优化性能;社区文档完善,问题解决方案丰富,遇到问题时能快速找到参考案例(如你之前遇到的反射权限问题、映射问题,均有成熟解决方案)。
  4. 性能更优,支撑高并发业务场景在批量读写 Excel 场景下,EasyExcel 的解析 / 导出速度远超传统 POI 原生 API,同时资源占用更低,能够支撑后台批量导出报表、批量导入业务数据等高并发场景的需求。

2-EasyExcel可解决的问题

2-1-传统 POI 工具的核心缺点

Apache POI 是 Java 操作 Excel 的传统主流工具,但它存在诸多难以规避的缺点,在实际项目中容易引发技术问题和开发效率问题:

2-1-1-内存占用极高,处理大文件极易触发 OOM内存溢出

这是 POI 最致命的缺点,也是实际项目中最常遇到的问题。

  • POI 的核心解析模式是「DOM 模式」(文档对象模型):读取 Excel 文件时,会将整个 Excel 的所有数据(包括所有单元格、样式、公式、格式信息等)一次性加载到 JVM 内存中,生成完整的对象树(Workbook -> Sheet -> Row -> Cell)。
  • 对于小文件(几千行以内),该模式无明显问题;但当处理大文件(如 10 万行、100 万行数据,或多 sheet、大单元格内容的 Excel)时,内存会急剧飙升,很快触发OutOfMemoryError,导致应用崩溃。
  • 即使 POI 提供了 SXSSF(流式 XSSF)做优化(仅缓存指定行数的内存数据,多余数据写入临时文件),也存在明显局限:需要手动配置内存窗口大小,临时文件会占用磁盘空间,且仍无法彻底解决超大规模文件的内存问题,同时增加了代码复杂度。

2-1-2-底层 API 繁琐,开发效率极低,代码冗余度高

POI 的 API 设计偏向底层,操作 Excel 需要编写大量模板化、重复性代码,开发成本高、上手难度大:

  • 读取 Excel:需要手动创建Workbook(还要区分.xls的HSSFWorkbook和.xlsx的XSSFWorkbook)、Sheet、遍历Row和Cell,手动判断单元格类型(文本、数字、日期、布尔等),手动处理空值和数据类型转换,代码冗长且易出错。
  • 写入 Excel:需要手动创建工作表、设置表头、逐行创建单元格、填充数据、手动配置单元格样式(行高、列宽、字体等),即使是简单的导出功能,也需要几十行甚至上百行代码。
  • 示例(POI 简单读取 Excel 的冗余代码):需要手动处理格式判断、空值处理,对比 EasyExcel 差距明显。

2-1-3-Excel 格式兼容性差,易出现解析 / 写入异常

POI 对不同版本 Excel 的支持不够友善,手动兼容成本高:

  • 对 Excel 2003(.xls)和 Excel 2007+(.xlsx)采用两套完全不同的 API(HSSF 系列 vs XSSF 系列),开发时需要手动判断文件后缀名,选择对应的 API,若判断失误会直接导致解析失败或文件损坏。
  • 对非标准格式的 Excel 文件(如部分第三方工具生成的 Excel、带有特殊样式 / 公式的 Excel),解析时容易出现NullPointerException、IllegalStateException等异常,且排查困难。
  • 对合并单元格、多级表头的解析支持薄弱,需要手动编写复杂的逻辑来处理单元格合并关系,极易出现字段映射错误。

2-1-4-特殊数据类型转换繁琐,易出错

POI 不提供自动的数据类型映射能力,处理日期、数字、枚举等特殊类型时,需要手动编写转换逻辑:

  • 处理日期类型:需要手动判断单元格是否为日期类型,再通过DateUtil.getJavaDate()转换,若 Excel 中的日期格式不统一(如 yyyy-MM-dd、yyyy/MM/dd、带时分秒等),还需要手动适配,容易出现日期转换异常。
  • 处理数字类型:Excel 中的数字(如整数、小数)会被解析为Double类型,需要手动转换为Integer、Long等目标类型,容易出现精度丢失或类型转换错误。
  • 处理枚举类型:需要手动将 Excel 中的文本值与 Java 枚举值做映射,代码冗余且维护成本高。

2-1-5-复杂业务场景处理难度大,实现成本高

对于实际项目中的复杂 Excel 场景,POI 的实现逻辑极其复杂,开发和维护成本居高不下:

  • 复杂表头处理:针对多级表头、合并单元格表头,需要手动遍历表头行,分析单元格的合并范围和对应关系,才能实现表头与 Java 实体类的映射,逻辑繁琐且易出错。
  • 模板导出:基于 Excel 模板填充数据导出(如报表导出),POI 需要手动定位模板中的填充位置,处理动态列表、样式保留等问题,代码复杂度极高。
  • 自定义样式批量导出:若需要给导出的 Excel 设置统一表头样式、数据行样式、条件格式等,POI 需要手动创建CellStyle、Font等对象,逐个单元格应用样式,代码冗余且性能低下。

2-1-6- 性能表现不佳,难以支撑高并发 / 批量场景

在批量 Excel 读写(如批量导入业务数据、批量导出报表)、高并发访问场景下,POI 的性能短板尤为突出:

  • 批量读取 / 写入时,由于内存占用高、API 底层开销大,处理速度缓慢,容易导致服务器线程阻塞。
  • 高并发场景下(如多个用户同时导出大报表),POI 会快速耗尽服务器内存和 CPU 资源,导致应用响应缓慢甚至宕机。

2-1-7-代码可维护性差,后期修改成本高

POI 的代码模板化严重,业务逻辑与 Excel 操作逻辑耦合度高,后期维护困难:

  • 若需要修改 Excel 表头名称、调整列顺序、增减字段,需要大面积修改 POI 的遍历和映射代码,容易引入新的 BUG。
  • 冗余的模板化代码可读性差,新接手的开发人员需要花费大量时间理解代码逻辑,增加项目维护成本。

3-EasyExcel 对应解决方案(精准攻克 POI 痛点)

针对上述 POI 的 7 大核心缺点,EasyExcel 提供了针对性的解决方案,完美规避传统工具的痛点:

Java开发常用工具-01-EasyExcel

补充:关键解决方案的落地示例(直观对比)

  1. 解决 POI 内存溢出问题:EasyExcel 流式读取无需缓存全量数据
    • POI:读取 10 万行数据会一次性加载所有行到内存,触发 OOM;
    • EasyExcel:逐行读取,读取一行处理一行,处理完成后释放该行内存,内存占用稳定在几十 MB 以内。
  1. 解决 POI API 繁琐问题:EasyExcel 几行代码实现 Excel 读取
    • POI:需要几十行代码处理 Workbook、Sheet、Row、Cell 的创建和遍历;
    • EasyExcel:通过注解映射实体类后,仅需 3 行核心代码即可读取 Excel 数据(对应你之前的导入场景):
List<BigDataTableAssetsInfoExcelVo> dataList = EasyExcel.read(excelFile)
    .head(BigDataTableAssetsInfoExcelVo.class)
    .sheet()
    .doReadSync();
  1. 解决 POI 数据转换繁琐问题:EasyExcel 注解自动转换日期
    • POI:需要手动判断单元格类型,手动转换日期格式,代码冗余;
    • EasyExcel:仅需添加注解,自动完成 Excel 日期与 Java 字符串 / Date 类型的转换:
@ExcelProperty("ddl变更时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss") // 自动按该格式转换
private String ddlChangeTime;

3-常用导出工具的工作原理

Java开发常用工具-01-EasyExcel

1. 输入源处理:数据初始加载逻辑

通用逻辑:接收外部.xlsx 文件,完成初始读取准备。

POI 原理:采用 “全量内存加载” 策略,直接将整个 xlsx 文件读取至 JVM 堆内存,后续所有操作均基于内存中的文件副本。

EasyExcel 原理:采用 “磁盘缓存加载” 策略,仅读取文件元信息,将文件主体内容暂存至磁盘临时缓存区,不直接占用堆内存。

2. 解压缩环节:资源提取核心差异

通用逻辑:解压 xlsx 压缩包,提取内部 XML 及关联资源,为后续解析做准备。

POI 原理:内存全量解压。解压过程在内存中完成,将压缩包内所有 XML 文件、样式资源一次性加载到内存,形成完整资源集合,供后续解析调用。

EasyExcel 原理:磁盘流式解压。基于解析进度按需解压,仅将当前待处理的目标 XML 文件(如当前解析的 sheet 对应的 xml)解压至磁盘临时文件,无需加载全部资源,从源头控制内存占用。

3. XML 解析环节:数据读取底层机制

通用逻辑:通过 XML 解析器读取解压后的表格数据,此环节是内存控制的核心节点。

POI 原理:虽基于 SAX(事件驱动型解析)框架,但未完全遵循流式特性,而是先将整个 XML 文件加载到内存并构建 DOM 树(文档对象模型),再遍历 DOM 树提取数据。DOM 树需占用大量内存存储节点关系,导致内存消耗随 XML 文件体积线性增长。

EasyExcel 原理:纯 SAX 流式解析。完全遵循事件驱动模型,从磁盘逐行读取 XML 数据流,每解析一行数据就触发对应事件(如 “行开始”“单元格数据”“行结束”),仅在内存中暂存当前行数据,不构建完整 DOM 树,实现 “读一行、处理一行、释放一行”。

4. 模型转换环节:数据对象映射逻辑

通用逻辑:将 XML 原始数据转换为程序可操作的对象模型,消除原始 XML 的格式依赖。

POI 原理:构建 “层级化全量内存对象”。将数据映射为Workbook(工作簿)→Sheet(工作表)→Row(行)→Cell(单元格)的完整对象树,所有对象全量存储在内存中,支持随机访问任意单元格数据,但内存开销极大。

EasyExcel 原理:实现 “逐行实体映射”。通过@ExcelProperty等注解,将解析到的每一行数据即时映射为单个 Java 实体类对象,内存中仅保留当前行对应的实体对象,解析完成后立即释放内存,无冗余对象存储。

5. 结果返回环节:数据交付模式差异

通用逻辑:将处理后的数据交付给上层应用,支撑业务操作。

POI 原理:全量一次性返回。待所有数据解析完成并构建完对象树后,将整个Workbook对象或Sheet对象一次性返回,上层应用可随机操作任意数据,但需等待全量解析完成。

EasyExcel 原理:流式逐行返回。基于监听器(Listener)模式,每解析完成一行并转换为实体类后,立即通过监听器回调将数据返回给上层应用,实现 “解析与业务处理并行”,无需等待全量数据。

10-Springboot集成Easyexcel实现导出功能

10-1-添加pom依赖


<properties>
     <java.version>11</java.version>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
     <xxl-job.version>2.4.0</xxl-job.version>
     <cglib.version>3.3.0</cglib.version>
     <easyexcel.version>2.2.10</easyexcel.version>
  </properties>
<!--  easyexcel  begin -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>${easyexcel.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>cglib</artifactId>
            <groupId>cglib</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>${cglib.version}</version>
</dependency>
<!--  easyexcel  end -->

10-2-配置Excel的目录

#Excel存储目录
excel:
  # 方式1-绝对路径(Win D:excel_files;Linux /usr/local/excel_files)
  dir: D:excel_files
  # 方式2:相对路径(项目根目录下的 excel_files 文件夹,推荐开发环境使用)
  # dir: ./excel_files
  name: 数据资产录入_表资产

10-3-创建EasyExcel的工具类-ExcelUtil

package com.cn.bp.bmp.file.service.biz;

import com.cn.bp.bmp.file.service.application.command.response.ExcelVo;
import com.cn.bp.bmp.file.service.interfaces.utils.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Excel业务处理层
 * 调用ExcelUtil实现指定目录Excel读取、导出等功能
 */
@Slf4j
@Service
public class ExcelBizService {

    // 从配置文件注入指定Excel目录
    @Value("${excel.dir}")
    private String excelLocalDir;

    /**
     * 读取指定目录下的单个Excel文件
     * @param excelFileName Excel文件名(如:user_data.xlsx)
     * @return Excel数据列表
     * @throws IOException 读取异常
     */
    public List<ExcelVo> readLocalExcel(String excelFileName) throws IOException {
        // 1. 拼接本地Excel文件完整路径(自动适配Windows/Linux路径分隔符)
        String localExcelPath = excelLocalDir + File.separator + excelFileName;
        log.info("准备读取本地Excel文件:{}", localExcelPath);

        // 2. 调用扩展后的ExcelUtil方法读取本地文件
        List<ExcelVo> excelVoList = ExcelUtil.importLocalExcel(localExcelPath, ExcelVo.class);

        // 3. 打印读取结果(可根据业务需求做后续处理:如入库、数据校验等)
        log.info("本地Excel文件读取成功,共读取{}条数据", excelVoList.size());
        return excelVoList;
    }

    /**
     * 可选:读取指定目录下所有Excel文件
     */
    public void readAllLocalExcel() {
        File dirFile = new File(excelLocalDir);
        // 校验目录是否存在
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            log.error("指定的Excel目录不存在:{}", excelLocalDir);
            return;
        }

        // 过滤出.xlsx/.xls格式文件
        File[] excelFiles = dirFile.listFiles((file, name) -> 
            name.endsWith(".xlsx") || name.endsWith(".xls")
        );

        if (excelFiles == null || excelFiles.length == 0) {
            log.warn("指定Excel目录下无有效Excel文件:{}", excelLocalDir);
            return;
        }

        // 遍历读取所有Excel文件
        for (File file : excelFiles) {
            try {
                List<ExcelVo> excelVoList = ExcelUtil.importLocalExcel(file.getAbsolutePath(), ExcelVo.class);
                log.info("读取Excel文件{}成功,数据量:{}", file.getName(), excelVoList.size());
            } catch (IOException e) {
                log.error("读取Excel文件{}失败", file.getName(), e);
            }
        }
    }
}

10-4-创建接收Excel数据的Vo

package com.cn.bp.bmp.file.service.application.command.response;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;

/**
* @description:  Bigdatatableassetsinfo实体类
* @author Mindy
*/
@HeadRowHeight(value = 40)
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "BigDataTableAssetsInfoExcelVo ")
public class BigDataTableAssetsInfoExcelVo implements Serializable {

    /**
     * 负责人
     */
    @ExcelProperty(value = "负责人", index = 0)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "负责人", name = "owner")
    private String owne;


    /**
     * 业务系统
     */
    @ExcelProperty(value = "业务系统", index = 1)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "业务系统", name = "businessSystem")
    private String businessSystem;


    /**
     * 业务域/主题域
     */
    @ExcelProperty(value = "业务域/主题域", index = 2)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "业务域/主题域", name = "businessDomain")
    private String businessDomain;


    /**
     * 表名
     */
    @ExcelProperty(value = "表名", index = 4)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "表名", name = "tableName")
    private String tableName;


    /**
     * 描述
     */
    @ExcelProperty(value = "描述", index = 5)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "描述", name = "description")
    private String description;

    /**
     * 审批状态
     */
    @ExcelProperty(value = "审批状态", index = 14)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "审批状态", name = "approvalStatus")
    private String approvalStatus;


    /**
     * 审批意见
     */
    @ExcelProperty(value = "审批意见", index = 15)
    @ColumnWidth(value = 20)
    @ApiModelProperty(value = "审批意见", name = "approvalComment")
    private String approvalComment;

}

10-5-自定义监听器

针对大文件(10 万条 + 数据),需自定义监听器继承 AnalysisEventListener,实现流式批量处理,避免 OOM

package com.cn.bp.bmp.file.service.interfaces.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.cn.bp.bmp.file.service.application.command.response.UserExcelVo;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义Excel监听器(大文件读取专用)
 * 特点:流式读取,批量处理,避免内存溢出
 */
@Slf4j
public class UserExcelListener extends AnalysisEventListener<UserExcelVo> {
    /**
     * 批量处理阈值(每100条数据处理一次,可根据业务调整)
     */
    private static final int BATCH_THRESHOLD = 100;

    /**
     * 临时存储读取到的数据
     */
    private List<UserExcelVo> tempDataList = new ArrayList<>(BATCH_THRESHOLD);

    /**
     * 业务处理接口(通过构造方法传入,避免监听器中无法注入Spring Bean)
     */
    private final UserExcelBizService userExcelBizService;

    /**
     * 构造方法:传入业务服务类
     * @param userExcelBizService 业务处理类
     */
    public UserExcelListener(UserExcelBizService userExcelBizService) {
        this.userExcelBizService = userExcelBizService;
    }

    /**
     * 每条数据读取完成后调用(核心方法1)
     * @param data 单条Excel数据
     * @param context 分析上下文
     */
    @Override
    public void invoke(UserExcelVo data, AnalysisContext context) {
        log.info("读取到Excel单条数据:{}", data);
        // 1. 将单条数据加入临时列表
        tempDataList.add(data);
        // 2. 达到批量阈值时,执行业务处理并清空临时列表
        if (tempDataList.size() >= BATCH_THRESHOLD) {
            batchProcessData();
            tempDataList.clear();
        }
    }

    /**
     * 所有数据读取完成后调用(核心方法2)
     * @param context 分析上下文
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 处理剩余的不足批量阈值的数据
        if (!tempDataList.isEmpty()) {
            batchProcessData();
            log.info("Excel所有数据读取完成,剩余数据处理完毕");
        } else {
            log.info("Excel所有数据读取完成,无剩余数据");
        }
    }

    /**
     * 批量处理数据(调用业务层方法)
     */
    private void batchProcessData() {
        try {
            // 调用业务层方法,批量保存/处理数据
            userExcelBizService.batchSaveUserExcelData(tempDataList);
            log.info("批量处理Excel数据成功,处理条数:{}", tempDataList.size());
        } catch (Exception e) {
            log.error("批量处理Excel数据失败", e);
            throw new RuntimeException("批量处理Excel数据失败:" + e.getMessage());
        }
    }
}

10-6-业务层接口(统一业务处理)

创建业务层接口,封装 Excel 数据的业务逻辑(如批量保存到数据库)。

package com.cn.bp.bmp.file.service.biz;

import com.cn.bp.bmp.file.service.application.command.response.UserExcelVo;
import java.util.List;

/**
 * Excel读取业务处理接口
 */
public interface UserExcelBizService {
    /**
     * 批量保存Excel读取到的用户数据
     * @param userExcelVoList 用户Excel数据列表
     */
    void batchSaveUserExcelData(List<UserExcelVo> userExcelVoList);
}

10-7-业务层实现类

package com.cn.bp.bmp.file.service.biz.impl;

import com.cn.bp.bmp.file.service.application.command.response.UserExcelVo;
import com.cn.bp.bmp.file.service.biz.UserExcelBizService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * Excel读取业务处理实现类
 */
@Slf4j
@Service
public class UserExcelBizServiceImpl implements UserExcelBizService {
    /**
     * 批量保存用户Excel数据(实际业务中可替换为保存到数据库等操作)
     * @param userExcelVoList 用户Excel数据列表
     */
    @Override
    public void batchSaveUserExcelData(List<UserExcelVo> userExcelVoList) {
        // 模拟业务处理:此处可替换为MyBatis/JPA的批量插入操作
        log.info("批量保存用户Excel数据,条数:{},数据详情:{}", userExcelVoList.size(), userExcelVoList);
        // 示例:userMapper.batchInsert(userExcelVoList);
    }
}

10-8-小文件上传读取

10-8-1-接口层

package com.cn.bp.bmp.file.service.controller;

import com.cn.bp.bmp.file.service.application.command.response.UserExcelVo;
import com.cn.bp.bmp.file.service.biz.UserExcelBizService;
import com.cn.bp.bmp.file.service.interfaces.utils.ExcelUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

/**
 * Excel读取控制层
 */
@Slf4j
@RestController
@RequestMapping("/api/excel")
@RequiredArgsConstructor // 构造方法注入Bean,避免Autowired
public class ExcelReadController {
    private final UserExcelBizService userExcelBizService;

    /**
     * 场景1:小文件上传读取(同步读取,1万条以内推荐)
     * @param file 上传的Excel文件(.xlsx/.xls)
     * @return 读取结果
     */
    @PostMapping("/read/small/upload")
    public String readSmallExcelByUpload(@RequestParam("file") MultipartFile file) {
        try {
            // 1. 调用ExcelUtil工具类,同步读取Excel数据
            List<UserExcelVo> userExcelVoList = ExcelUtil.importData(file, UserExcelVo.class);
            log.info("小文件上传读取成功,数据条数:{}", userExcelVoList.size());

            // 2. 调用业务层,处理读取到的数据
            userExcelBizService.batchSaveUserExcelData(userExcelVoList);

            return "小文件上传读取成功!数据条数:" + userExcelVoList.size();
        } catch (Exception e) {
            log.error("小文件上传读取失败", e);
            return "小文件上传读取失败:" + e.getMessage();
        }
    }
}

10-8-2-测试

  1. 准备一个少于 1 万条数据的 Excel 文件,表头为「ID、姓名、年龄、薪资、入职时间」;
  2. 使用 PostMan/ApiPost 等工具,以 POST 方式请求 http://localhost:8080/api/excel/read/small/upload;
  3. 请求方式选择 form-data,键为 file,值选择准备好的 Excel 文件,发送请求即可。

Java开发常用工具-01-EasyExcel

10-9-小文件本地读取

适用于 本地服务器上的 Excel 文件(非上传),同步读取所有数据,无需前端上传。

10-9-1-接口层

package com.cn.bp.bmp.file.service.biz.impl;

import com.cn.bp.bmp.file.service.application.command.response.UserExcelVo;
import com.cn.bp.bmp.file.service.biz.UserExcelBizService;
import com.cn.bp.bmp.file.service.interfaces.utils.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * Excel读取业务处理实现类
 */
@Slf4j
@Service
public class UserExcelBizServiceImpl implements UserExcelBizService {
    /**
     * 批量保存用户Excel数据(实际业务中可替换为保存到数据库等操作)
     * @param userExcelVoList 用户Excel数据列表
     */
    @Override
    public void batchSaveUserExcelData(List<UserExcelVo> userExcelVoList) {
        // 模拟业务处理:此处可替换为MyBatis/JPA的批量插入操作
        log.info("批量保存用户Excel数据,条数:{},数据详情:{}", userExcelVoList.size(), userExcelVoList);
        // 示例:userMapper.batchInsert(userExcelVoList);
    }

    /**
     * 场景2:本地小文件读取(指定文件路径)
     * @param localFilePath 本地Excel文件完整路径(如:D:/test/user.xlsx 或 ./excel/user.xlsx)
     * @return 读取结果提示
     */
    public String readSmallExcelByLocal(String localFilePath) {
        try {
            // 1. 调用ExcelUtil工具类,读取本地Excel文件
            List<UserExcelVo> userExcelVoList = ExcelUtil.importLocalExcel(localFilePath, UserExcelVo.class);
            log.info("本地小文件读取成功,数据条数:{}", userExcelVoList.size());

            // 2. 调用业务方法,处理数据
            this.batchSaveUserExcelData(userExcelVoList);

            return "本地小文件读取成功!数据条数:" + userExcelVoList.size();
        } catch (Exception e) {
            log.error("本地小文件读取失败,文件路径:{}", localFilePath, e);
            return "本地小文件读取失败:" + e.getMessage();
        }
    }
}

10-9-2-测试

package com.cn.bp.bmp.file.service;

import com.cn.bp.bmp.file.service.biz.UserExcelBizService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ExcelLocalReadTest {
    @Autowired
    private UserExcelBizService userExcelBizService;

    @Test
    public void testLocalExcelRead() {
        // 本地Excel文件路径
        String localFilePath = "./excel/user.xlsx";
        // 调用读取方法
        String result = ((UserExcelBizServiceImpl) userExcelBizService).readSmallExcelByLocal(localFilePath);
        System.out.println(result);
    }
}

Java开发常用工具-01-EasyExcel

© 版权声明

相关文章

暂无评论

none
暂无评论...