springboot excel 表格入门与实战

发布于:2025-09-12 ⋅ 阅读:(21) ⋅ 点赞:(0)

Spring Boot3 FastExcel 项目地址

https://gitee.com/supervol/loong-springboot-study

(记得给个start,感谢)

FastExcel 介绍

        FastExcel 是一款轻量、高性能的 Java Excel 处理工具,核心目标是解决传统库(如 Apache POI)在大数据量场景下的内存溢出问题,同时提供简洁易用的 API。其官方核心定位如下:

  • 开源许可:采用 Apache License 2.0,允许商业场景免费使用,无版权限制。
  • 跨 JDK 支持:兼容 JDK 8 ~ JDK 21(覆盖 Spring Boot 3 所需的 JDK 17+,同时支持旧项目)。
  • 底层依赖:基于 Apache POI 封装,但已优化内存模型;若项目已引入 POI,需手动排除冲突依赖。
  • 模块合并:2025 年 7 月后已将 fastexcel-core 合并入核心模块 fastexcel

FastExcel 特性

功能分类 核心能力
高性能读写 优化内存模型,相比传统 POI 减少 60%+ 内存占用,支持百万级数据无 OOM。
流式处理 原生支持流式读取 / 写入,无需手动处理内存缓冲,数据逐行处理,降低内存峰值。
简洁 API 基于静态工厂类 FastExcel 封装,3 行代码即可实现 Excel 读写,无需关注底层 Sheet/Row/Cell 层级。
注解驱动 通过 @ExcelProperty 映射列与实体类,@ExcelIgnore 忽略无需导出的字段,配置零侵入。
扩展性 提供 ReadListener 监听读取过程(如数据校验、批量入库),支持自定义转换器。
多格式支持 兼容 .xlsx(Office 2007+)、.xls(兼容模式),无需额外配置格式类型。

FastExcel 示例

        请参考项目地址中 springboot-office/springboot-excel 模块代码。

FastExcel 实战

        FastExcel 官方核心 API 围绕 FastExcel 静态类展开,读取需实现 ReadListener,写入通过注解映射实体类,无复杂 Builder 嵌套。

1. 定义 Excel 映射实体类

        使用官方注解 @ExcelProperty指定列名,@ExcelIgnore 忽略无需导出的字段:

import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import java.util.Date;

// 官方示例实体类:用户数据
public class UserData {
    // @ExcelProperty:指定Excel列名,与表头完全匹配
    @ExcelProperty("用户ID")
    private Long userId;

    @ExcelProperty("用户名")
    private String username;

    // 日期类型无需额外配置格式(默认 yyyy-MM-dd HH:mm:ss,支持自动转换)
    @ExcelProperty("注册时间")
    private Date registerTime;

    // @ExcelIgnore:该字段不写入Excel,也不读取
    @ExcelIgnore
    private String password;

    // 省略 getter/setter(必须提供,否则无法映射)
}

2. 读取 Excel 文件

        通过 实现 ReadListener 接口 处理读取逻辑(支持数据逐行回调,避免一次性加载内存),适用于本地文件或 HTTP 上传文件。

(1) 实现 ReadListener
import cn.idev.excel.listener.ReadListener;
import cn.idev.excel.context.AnalysisContext;
import java.util.ArrayList;
import java.util.List;

// 自定义读取监听器:处理Excel每行数据
public class UserDataListener implements ReadListener<UserData> {
    // 批量存储读取到的数据(可根据业务设置批量大小,如1000条入库一次)
    private List<UserData> dataList = new ArrayList<>(1000);

    // 逐行回调:每读取一行数据,触发该方法
    @Override
    public void invoke(UserData userData, AnalysisContext context) {
        // 1. 数据校验(示例:用户名不能为空)
        if (userData.getUsername() == null || userData.getUsername().trim().isEmpty()) {
            throw new RuntimeException("第" + context.getRowNum() + "行:用户名不能为空");
        }
        // 2. 加入临时列表
        dataList.add(userData);
        // 3. 批量处理(示例:满1000条入库,避免列表过大)
        if (dataList.size() >= 1000) {
            saveBatchData(); // 调用业务方法批量入库
            dataList.clear(); // 清空列表,释放内存
        }
    }

    // 读取完成后回调:所有数据读取完毕后触发
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 处理剩余数据(不足1000条的部分)
        if (!dataList.isEmpty()) {
            saveBatchData();
        }
        System.out.println("Excel读取完成,共处理" + context.getTotalRowNum() + "行数据");
    }

    // 业务方法:批量保存数据(示例,需结合DAO层实现)
    private void saveBatchData() {
        // userMapper.batchInsert(dataList); // MyBatis批量入库示例
        System.out.println("批量保存" + dataList.size() + "条用户数据");
    }
}
(2) 发起读取

场景 A:读取本地 Excel 文件

import cn.idev.excel.FastExcel;
import org.springframework.stereotype.Service;

@Service
public class ExcelImportService {
    // 读取本地Excel文件(路径示例:D:/data/user_import.xlsx)
    public void importLocalExcel(String filePath) {
        // 官方读取API:FastExcel.read(文件路径, 实体类, 监听器).sheet().doRead()
        FastExcel.read(filePath, UserData.class, new UserDataListener())
                .sheet() // 读取第一个工作表(默认索引0,可指定名称:.sheet("用户导入表"))
                .doRead(); // 执行读取
    }
}

场景 B:读取 HTTP 上传文件

import cn.idev.excel.FastExcel;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;

@RestController
public class ExcelImportController {
    // 接收前端上传的Excel文件
    @PostMapping("/api/excel/import/user")
    public String importUser(@RequestParam("file") MultipartFile file) throws IOException {
        // 1. 校验文件格式
        String fileName = file.getOriginalFilename();
        if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
            return "错误:仅支持 .xlsx 或 .xls 格式";
        }

        // 2. 读取上传文件流(无需手动关闭流,FastExcel内部自动处理)
        FastExcel.read(file.getInputStream(), UserData.class, new UserDataListener())
                .sheet()
                .doRead();

        return "导入成功,文件大小:" + file.getSize() + "字节";
    }
}

3. 写入 Excel 文件

        写入 API 极简,通过 FastExcel.write() 直接生成文件,支持流式写入大数据量(无需手动开启,内部自动判断)。Spring Boot 响应式下载示例代码。

import cn.idev.excel.FastExcel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

@RestController
public class ExcelExportController {
    private final UserService userService; // 业务层:查询用户数据

    // 构造注入(Spring Boot 3 推荐)
    public ExcelExportController(UserService userService) {
        this.userService = userService;
    }

    // 导出用户数据为Excel,浏览器直接下载
    @GetMapping("/api/excel/export/user")
    public void exportUser(HttpServletResponse response) throws IOException {
        // 1. 准备导出数据(模拟大数据量,如10万条)
        List<UserData> userList = userService.listAllUsers(); // 业务查询(建议分页查询避免内存占用)

        // 2. 设置响应头(指定下载格式、文件名)
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        // 文件名编码:避免中文乱码
        String fileName = URLEncoder.encode("用户数据导出_2025.xlsx", "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

        // 3. 官方写入API:流式写入响应流
        FastExcel.write(response.getOutputStream(), UserData.class)
                .sheet("用户列表") // 指定工作表名称
                .doWrite(userList); // 执行写入(内部自动流式处理,无OOM风险)
    }
}

4. 自定义读取 / 写入配置

        支持自定义工作表索引、表头行数、数据转换器等,通过链式调用扩展配置:

(1) 自定义读取配置

// 读取第2个工作表(索引1),跳过前2行(表头在第3行)
FastExcel.read(filePath, UserData.class, new UserDataListener())
        .sheet(1) // 工作表索引(0开始),也可指定名称:.sheet("2025年用户")
        .headRowNumber(2) // 表头行数(默认1行,即第1行为表头,此处表示前2行为说明,第3行为表头)
        .doRead();

(2) 自定义日期格式

import cn.idev.excel.converter.LocalDateTimeConverter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// 示例:自定义LocalDateTime格式为 "yyyy年MM月dd日 HH:mm"
public class CustomDateTimeConverter extends LocalDateTimeConverter {
    public CustomDateTimeConverter() {
        super(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm"));
    }
}

// 实体类中使用自定义转换器
public class UserData {
    @ExcelProperty(value = "最后登录时间", converter = CustomDateTimeConverter.class)
    private LocalDateTime lastLoginTime;
    // 其他字段...
}

FastExcel 注意

  1. POI 依赖冲突:FastExcel 内部依赖 POI 4.4.0+,若项目已引入旧版 POI(如 3.x),需手动排除冲突,否则会出现 NoSuchMethodError
  2. 实体类要求:映射实体类必须提供 无参构造器 和 getter/setter(FastExcel 通过反射获取字段值,缺少会导致映射失败)。
  3. 大数据量建议:导出超过 100 万条数据时,建议结合 分页查询(如 MyBatis 分页),分批次调用 doWrite()(示例:每次写入 10 万条,避免一次性查询全量数据)。
  4. 文件权限:读取本地文件时,确保应用有文件读取权限;写入临时文件时,避免使用系统敏感路径(如 /root)。