Spring Boot 实现主表+明细表 Excel 导出(EasyPOI 实战)

发布于:2025-07-03 ⋅ 阅读:(23) ⋅ 点赞:(0)

Spring Boot 实现主表+明细表 Excel 导出(EasyPOI 实战)

本文基于 Spring Boot + MyBatis-Plus + EasyPOI 实现多个业务模块)的主表带明细表导出 Excel 功能,涵盖多条件筛选、主表明细组装、批量导出,附详细示例与优化建议。


前言

在日常生产管理系统开发中,数据导出 Excel 报表是极为常见的需求,无论是生产数据、检测记录,还是统计分析结果,用户通常都希望能够一键导出,方便后续归档、分析和汇报。而很多业务场景不仅仅是导出单表数据,更常见的是主表+明细表的关联数据导出,比如:

  • 产品批次+检测明细
  • 项目记录+工序明细
  • 订单信息+订单项详情

如果不采用成熟方案,自己拼接 Apache POI 导出代码,工作量大、代码复杂、后期维护困难。因此,本文基于Spring Boot + MyBatis-Plus + EasyPOI框架,整理了一套主表带明细表 Excel 导出的完整实现方案。

希望这篇文章能帮到正在开发或即将实现类似功能的你,少走一些弯路,高效完成稳定可靠的数据导出模块。


一、EasyPOI简介

EasyPOI 是一个优秀的基于 Apache POI 封装的 Excel 导入导出框架,注解驱动,简单高效,特别适合 Spring Boot 场景。
📖 什么是 EasyPOI?
EasyPOI 是一款基于 Apache POI 封装的 Java Excel 文档导入导出工具,专门面向企业级 Java Web 开发场景,简化了 Excel、Word 等 Office 文件的读写操作。
相比原生 Apache POI,EasyPOI 在 API 设计、注解驱动、复杂表格映射、导出样式设置等方面做了高度封装,大大降低了开发门槛,常见的导入导出需求无需手动拼接行列、设置单元格样式,只需配置注解即可完成。

📌 核心特性:
📑 基于注解映射:通过 @Excel、@ExcelCollection 等注解,自动将实体类与 Excel 列映射,简化开发流程。

📄 支持多表头、多sheet、多级嵌套:便捷实现主表+明细表、一对多数据结构的导出。

🔍 支持模板导出:通过预设 Excel 模板,填充动态数据,保持样式不变。

📥📤 导入、导出功能完善:支持 Excel 03(.xls)、Excel 07(.xlsx)双格式导入导出。

🎨 支持丰富的样式设置:如字体、颜色、行高列宽、对齐方式、冻结行列、隐藏列、下拉框校验等。

📊 基于 Apache POI,稳定可靠,性能优良。

📌 常见 Java Excel 读写方案对比

工具库 特点 使用复杂度 功能完整性 优劣分析
Apache POI 原生 Java Office 文件操作库,功能全面 功能全但编码繁琐,手动操作行列单元格,样式复杂
EasyExcel (阿里开源) 性能优异,超大 Excel 文件读写,事件驱动型 擅长百万级数据导入导出,但复杂表头、嵌套结构、模板样式不如 EasyPOI
JXLS 基于 Excel 模板导出,表达式驱动 适合固定格式模板报表,但灵活性和复杂结构支持有限
EasyPOI 注解驱动,快速开发,复杂表头、明细、模板导出都支持 易上手,功能全面,适合企业级应用场景

📌 为什么选择 EasyPOI?

在我们实际项目开发中,比如OD密度检测记录、颗粒度检测记录、应力ORT记录等,需要导出主表+多条明细表的 Excel 文件,且要求:

导出文件带多级表头

支持不同检测类型记录共用导出方法

明细与主表数据一键映射

导出样式统一美观、支持模板样式

功能健全、简单易上手、便于后续维护

而经过对比:

原生 Apache POI → 功能太底层,开发效率低,维护成本高

EasyExcel → 虽然性能好,但主表+明细表嵌套导出、复杂表头样式不太方便

EasyPOI → 完全满足我们这类典型生产管理系统的数据导出需求,注解式开发,主表+明细表一对多映射超简单,支持模板,支持丰富样式,社区活跃,文档齐全

因此,我们最终选择了 EasyPOI 作为本项目 Excel 导入导出模块的核心组件。

二、使用步骤

1.pom文件导入相关依赖

代码如下(示例):

<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

2.实体类加上配置 注解 @Excel(name = XXX)

主表,代码如下(示例):
其中涉及到集合的明细表使用***@ExcelCollection(name =xx)注解***

@Data
public class DfOrtStressResult {
    @Excel(name = "项目")
    private String project;
    
    @Excel(name = "批次")
    private String batch;

    @ExcelCollection(name = "丝印-应力明细")
    private List<DfOrtStressDetail> dfOrtStressDetailList;
}

明细表,代码如下(示例):
其中有日期格式的使用 ***@Excel(name = “测试时间”, format = “yyyy-MM-dd HH:mm:ss”)***注解

@Data
public class DfOrtStressDetail {
    @Excel(name = "测试时间", format = "yyyy-MM-dd HH:mm:ss")
    private Date testTime;
    
    @Excel(name = "CS值")
    private Double cs;
}

⚠️ 注意:

@ExcelCollection 注解用于主表集合属性,EasyPOI自动关联导出子表内容。

3.Controller导出接口实现

📌 多条件筛选 + 主表查询

QueryWrapper<DfOrtStressResult> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(batch)) {
    queryWrapper.like("batch", batch);
}
List<DfOrtStressResult> resultList = dfOrtStressResultService.list(queryWrapper);

📌 批量查询明细 + 分组

List<String> batchList = resultList.stream().map(DfOrtStressResult::getBatch).distinct().collect(Collectors.toList());

List<DfOrtStressDetail> detailList = dfOrtStressDetailService.list(
    new QueryWrapper<DfOrtStressDetail>().in("batch", batchList)
);

Map<String, List<DfOrtStressDetail>> detailMap = detailList.stream()
    .collect(Collectors.groupingBy(DfOrtStressDetail::getBatch));

resultList.forEach(result -> 
    result.setDfOrtStressDetailList(detailMap.getOrDefault(result.getBatch(), new ArrayList<>()))
);

📌 Excel导出

ExportParams exportParams = new ExportParams("应力ORT记录", "ORT导出记录");
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, DfOrtStressResult.class, resultList);

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("应力ORT导出.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

workbook.write(response.getOutputStream());
workbook.close();

4.主表+明细组装技巧

核心原则:
✔ 批量查主表
✔ 提取批次集合
✔ 批量查明细,groupingBy 分组
✔ forEach 挂载明细

优点:
✅ 避免 N+1 查询
✅ 查询效率高
✅ 结构清晰,易维护

5. Excel导出效果

  • 主表字段 → 顶层表头
  • 明细字段 → @ExcelCollection 子表表头
  • 多条明细自然关联到对应主表行

6. 总结与优化建议

✔ EasyPOI注解简单直观,适合中小型数据导出场景
✔ 批次+分组查询避免性能问题
✔ Excel导出前建议限制数据量,或分页导出

进阶可用:

  • EasyExcel 替代大数据量导出
  • 动态列头/动态 sheet 导出

三、完整代码

    @GetMapping("/exportDfOrtStressResult")
    @ApiOperation(value = "导出记录及明细Excel")
    public void exportDfOrtStressResult(
            @RequestParam(required = false) String batch,
            @RequestParam(required = false) String project,
            @RequestParam(required = false) String color,
            @RequestParam(required = false) String process,
            HttpServletResponse response) throws IOException {

        // 查询主表条件
        QueryWrapper<DfOrtStressResult> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(batch)) {
            queryWrapper.like("batch", batch);
        }
        if (StringUtils.isNotBlank(project)) {
            queryWrapper.like("project", project);
        }
        if (StringUtils.isNotBlank(color)) {
            queryWrapper.like("color", color);
        }
        if (StringUtils.isNotBlank(process)) {
            queryWrapper.like("process", process);
        }
        queryWrapper.orderByDesc("create_time");

        // 查询主表数据
        List<DfOrtStressResult> resultList = dfOrtStressResultService.list(queryWrapper);

        if (!resultList.isEmpty()) {
            // 批量提取批次
            List<String> batchList = resultList.stream()
                    .map(DfOrtStressResult::getBatch)
                    .filter(Objects::nonNull)
                    .distinct()
                    .collect(Collectors.toList());

            if (!batchList.isEmpty()) {
                // 查询所有明细
                for (DfOrtStressResult dfOrtStressResult : resultList) {
                    QueryWrapper<DfOrtStressDetail> detailWrapper = new QueryWrapper<>();
                    detailWrapper.eq("batch", dfOrtStressResult.getBatch());
                    detailWrapper.eq("project", dfOrtStressResult.getProject());
                    detailWrapper.eq("color", dfOrtStressResult.getColor());
                    detailWrapper.eq("process", dfOrtStressResult.getProcess());

                    List<DfOrtStressDetail> list = dfOrtStressDetailService.list(detailWrapper);
                    dfOrtStressResult.setDfOrtStressDetailList(list);
                }
            }
        }

        // 导出Excel
        ExportParams exportParams = new ExportParams("ORT记录", "记录");
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, DfOrtStressResult.class, resultList);

        // 设置响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        String fileName = URLEncoder.encode("记录导出.xlsx", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

        // 写出Excel
        workbook.write(response.getOutputStream());
        workbook.close();
    }

📌 接口调用示例:

GET http://localhost:8080/你的路径/exportDfOrtStressResult?batch=202406&project=XX&color=XX&process=XX

***⚠️ 注意:***在postman测试工具上可能测试不了,需要在页面直接输入地址进行测试


总结

导出是生产项目中常用功能,合理利用 EasyPOI+MyBatis-Plus 查询组装技巧,不仅提升开发效率,也保障系统稳定性。👍
本文介绍了基于Spring Boot+MyBatis-Plus+EasyPOI实现主表带明细表Excel导出的完整方案。通过EasyPOI的@Excel和@ExcelCollection注解,可以轻松映射实体类与Excel列的关系。文章详细讲解了从多条件筛选、批量查询明细到Excel导出的实现步骤,提供了主表明细数据组装的高效技巧,并对比了常见Java Excel读写方案。该方案避免了原生POI的复杂性,实现了简洁高效的数据导出功能。


网站公告

今日签到

点亮在社区的每一天
去签到