后端常用技能:基于easy-poi实现excel一对多、多对多导入导出【附带源码】
0. 引言
在业务系统开发中,我们经常遇到excel导入导出的业务场景,普通的excel导入导出我们可以利用 apache poi、jxl以及阿里开源的easyexcel来实现,特别easyexcel更是将excel的导入导出极大简化,但是对于一些负载的表格形式,比如一条数据中再包含了多条子表数据的一对多场景,还有多对多场景,这类场景时easyexcel相对支持较弱。
于是今天我们就来看看如何通过apache easy-poi库来实现excel一对多、多对多导入导出的功能
1. easy-poi介绍
easy-poi是一个基于Apache POI的Java端Excel 操作工具库,目的是为了简化java程序对excel文件的操作。该库提供了简单的API接口,支持excel的读写、格式化等,以及excel数据导出到pdf、word等文件。
官方地址:https://gitee.com/lemur/easypoi
easy-poi提供了3个版本的工具库:
- easypoi-base:
easypoi-base 是 easypoi的核心模块,提供了基本的 Excel 处理功能,如读取、写入、转换等。
它不依赖于 Spring Boot,可以在任何 Java 项目中使用。
这个模块主要包含了 EasyPOI 的核心 API,如 ExcelReader、ExcelWriter、SXSSFSheet 等。 - easypoi-web:
easypoi-web 是基于 Spring Boot 的 Web 模块,它扩展了 easypoi-base 的功能,主要用于在 Web 环境中处理 Excel 文件。
这个模块提供了基于 Spring MVC 的控制器和方法,使得可以通过 HTTP 请求来上传和下载 Excel 文件。
easypoi-web 支持文件上传、文件下载、Excel 表单提交等功能,适用于需要在前端界面和后端服务之间传输 Excel 文件的应用场景。 - easypoi-annotation:
easypoi-annotation 是 easypoi 的注解模块,它提供了一系列注解,用于简化对象与 Excel 表格之间的映射。
通过使用这些注解,可以非常方便地将 Java 对象转换为 Excel 文件,或者从 Excel 文件中读取数据到 Java 对象。
这个模块特别适合于需要将数据库表结构映射为 Excel 文件或者将 Excel 文件数据导入到数据库表中的场景。
2. 导入功能实现
作者提供使用案例:https://gitee.com/lemur/easypoi/blob/master/basedemo.md
2.1 一对一导入
1、引入easypoi依赖,这里我们项目环境是springboot 2.6.13,java 1.8版本,因为已经引入了spring-web依赖,这里就单独引入easypoi-base核心库即可
<dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.2.0</version></dependency>
另外引入下依赖hibernate-validator,用于支持校验注解,否则会报错
Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.4.1.Final</version></dependency>
2、创建实体类,通过@Excel注解标注excel列表和实体类字段的绑定关系,其中name属性要与导入的excel列名保持完全一致
@Data
public class DataInfo {@Excel(name = "姓名" )private String name;@Excel(name = "数量" )private Integer number;@Excel(name = "地址" )private String address;@Excel(name = "创建日期", format="yyyy-MM-dd", width = 24)private Date createDate;}
3、创建导入接口, 通过ExcelImportUtil.importExcel接口即可实现导入excel数据解析
@PostMapping("import")public List<DataInfo> importData(MultipartFile file) throws Exception {ImportParams params = new ImportParams();params.setTitleRows(1);params.setHeadRows(2);params.setNeedVerify(true);List<DataInfo> dataInfos = ExcelImportUtil.importExcel(file, DataInfo.class, params);return dataInfos;}
需要注意的是这里的TitleRows表示的是excel导入文件中的标题行的所在行数,HeadRows表示的是表头行的所在行数
如下图黄色部分所示,就是excel的标题行,蓝色部分就是表头行,如果没有标题,将其值设置为0或不设置即可(该值默认为0)

4、所用的模版文件如上图所示,注意列名与实体类中的name属性保持一致,否则会识别不到
2.2 一对多,多对多导入
1、要实现一对多导入,就需要通过ExcelImportUtil.importExcelMore方法,该方法返回一个ExcelImportResult对象:
该对象中的list字段就是解析出来的数据,failList是解析失败时的数据,verifyFail表示验证是否失败。workbook和failWorkbook就是对应解析成功和失败时的表格体对象

2、我们利用该方法书写一个工具类,实现导入方法
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows,Class<T> clazz) {if (file == null) {return null;}ImportParams params = new ImportParams();params.setTitleRows(titleRows);params.setHeadRows(headerRows);params.setNeedVerify(true);ExcelImportResult<T> result = null;try {InputStream inputStream = file.getInputStream();result = ExcelImportUtil.importExcelMore(inputStream, clazz, params);} catch (NoSuchElementException e) {// 日志记录错误log.error(String.format("导入数据为空: %s", ExceptionUtils.getStackTrace(e)));throw new RuntimeException("导入数据为空");} catch (Exception e) {// 日志记录错误log.error(String.format("导入失败: %s", ExceptionUtils.getStackTrace(e)));throw new RuntimeException("导入失败");}if (result == null) {return null;}if (result.isVerifyFail()) {// 如有需要,可以根据result.getFailWorkbook();获取到有错误的数据throw new RuntimeException("校验出错");}return result.getList();}
3、实体类中我们创建子类实体
@Data
public class DataInfoOrder {@Excel(name = "订单号")private String orderNo;@Excel(name = "价格")private BigDecimal price;@Excel(name = "商品类型")private String type;@Excel(name = "商品名称")private String name;
}
4、主类实体中通过@ExcelCollection注解声明子类,且在需要合并表头的单元格中添加needMerge = true,注意一对多的字段要写到最后
@Data
public class DataInfo {@Excel(name = "姓名" , needMerge = true)private String name;@Excel(name = "数量" , needMerge = true)private Integer number;@Excel(name = "地址" , needMerge = true)private String address;@Excel(name = "创建日期", format="yyyy-MM-dd", width = 24,needMerge = true)private Date createDate;@ExcelCollection(name = "订单信息")private List<DataInfoOrder> orderList;
其对应的导入模版如下图,可以看到需要合并的就是前面一对一的字段

5、如果需要多对多,则再添加一个@ExcelCollection即可
@Data
public class DataInfo {@Excel(name = "姓名" , needMerge = true)private String name;@Excel(name = "数量" , needMerge = true)private Integer number;@Excel(name = "地址" , needMerge = true)private String address;@Excel(name = "创建日期", format="yyyy-MM-dd", width = 24,needMerge = true)private Date createDate;@ExcelCollection(name = "订单信息")private List<DataInfoOrder> orderList;@ExcelCollection(name = "标签信息")private List<DataInfoTag> tagList;
}
模版如下,注意这里故意模拟了3种多对多产生的数据空缺情况:后者空缺、前者空缺、都不空缺,待会我们看看解析的数据是怎么样的

6、修改一下导入接口
@PostMapping("import")public List<DataInfo> importData(MultipartFile file) throws Exception {List<DataInfo> dataInfos = ExcelUtil.importExcel(file, 1, 2, DataInfo.class);return dataInfos;}
7、测试调用

8、解析返回数据如下,可以看到实际上表格中的空行子数据,也添加了一个空子对象,因为easypoi本身是通过构建识别workbook表格对象的方式来解析数据的,因为这些空行属于中间空行,上下都有值,因此会被识别为空对象,这算是一个待优化项,但主体数据正确解析了,实际我们再通过一个非空判断就可以过滤这些空子对象,也很好处理。
[{"name": "张三","number": 2,"address": "王府井","createDate": "2024-03-31T16:00:00.000+00:00","orderList": [{"orderNo": "2024040100001","price": 20,"type": "生鲜","name": "苹果"},{"orderNo": "2024040100002","price": 10,"type": "生鲜","name": "香蕉"}],"tagList": [{"tag": "送货上门","type": "物流","level": 1},{"tag": null,"type": null,"level": null}]},{"name": "李四","number": 2,"address": "中山中路","createDate": "2024-03-31T16:00:00.000+00:00","orderList": [{"orderNo": "2024040100003","price": 100,"type": "电器","name": "充电器"},{"orderNo": "2024040100004","price": 20000,"type": "电脑","name": "macbook"},{"orderNo": null,"price": null,"type": null,"name": null}],"tagList": [{"tag": "送货上门","type": "物流","level": 1},{"tag": "电子产品","type": "货物","level": 2},{"tag": "24小时达","type": "物流","level": 1}]},{"name": "王五","number": 2,"address": "中山中路","createDate": "2024-03-31T16:00:00.000+00:00","orderList": [{"orderNo": "2024040100005","price": 10,"type": "百货","name": "手机膜"},{"orderNo": "2024040100006","price": 200,"type": "百货","name": "电钻"}],"tagList": [{"tag": "送货上门","type": "物流","level": 1},{"tag": "24小时达","type": "物流","level": 1}]}
]
3. 导出功能实现
1、导入实现了,导出的实现就相对更加简单了,只需要调用ExcelExportUtil.exportExcel方法即可, 该方法需要三个参数:
- ExportParams对象,我们自己new一个,如果有导出样式要求,可以在该对象中定义

- Class 导出的实体类class,与导入时创建的实体类一个用法,字段上声明
@Excel注解,可以在其中声明数据格式、表格高度、宽度等,如果有一对多、多对多的子表导出,那么通过@ExcelCollection声明即可 - Collection<?> dataSet, 要导出的数据,其结构体与上述class参数保持一致
2、当然该方法是构建了一个Workbook对象,如果我们需要excel文件导出到浏览器,就需要将其文件数据输出为文件流,响应给前端,那么还需要用到响应体HttpServletResponse,以及Workbook的write方法,同时声明好数据类型content-Type为文件流
基础示例代码如下:
public static <T> void downLoadExcel(String fileName, HttpServletResponse response,Class clazz,Workbook workbook) throws RuntimeException{ExportParams params = new ExportParams();params.setSheetName("data");//设置sheet名try {// 兼容中文fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); response.setCharacterEncoding("utf-8");response.setHeader("content-Type", "application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=" + fileName);workbook.write(response.getOutputStream());} catch (IOException e) {// 一个自定义枚举 错误信息的e.printStackTrace();throw new RuntimeException("下载出错");}}
3、导出接口封装,同时造一下假数据:
@GetMapping("export")public void exportData(HttpServletResponse response){List<DataInfo> dataInfos = new ArrayList<>();for (int i = 1; i <= 10; i++) {DataInfo info = new DataInfo();info.setName("数据"+i);info.setAddress("地址"+i);info.setNumber(i);info.setCreateDate(new Date());Random rand = new Random();int num = rand.nextInt(5) + 1;int num2 = rand.nextInt(5) + 1;List<DataInfoOrder> orderList = new ArrayList<>(num);for (int j = 1; j <= num; j++) {DataInfoOrder order = new DataInfoOrder();order.setPrice(new BigDecimal(j));order.setName("商品"+j);order.setOrderNo("订单号"+j);order.setType("类型"+j);orderList.add(order);}List<DataInfoTag> tagList = new ArrayList<>();for (int j = 1; j <= num2; j++) {DataInfoTag tag = new DataInfoTag();tag.setTag("标签"+j);tag.setLevel(j);tag.setType("标签类型"+j);tagList.add(tag);}info.setOrderList(orderList);info.setTagList(tagList);dataInfos.add(info);}ExcelUtil.downLoadExcel("导出数据.xlsx", response, DataInfo.class, dataInfos);}
4、浏览器直接调用该导出接口

5、导出生成的excel如下图所示,有样式需要的,大家自行在ExportParams参数中调整即可

总结
如上,我们就实现了针对excel的一对一、一对多、多对多的导入导出功能,实际使用时,大家可以将方法进行二次封装,实现更加简洁方便的API
本文演示代码见:https://gitee.com/wuhanxue/wu_study/tree/master/demo/excel_import_demo
相关文章:
后端常用技能:基于easy-poi实现excel一对多、多对多导入导出【附带源码】
0. 引言 在业务系统开发中,我们经常遇到excel导入导出的业务场景,普通的excel导入导出我们可以利用 apache poi、jxl以及阿里开源的easyexcel来实现,特别easyexcel更是将excel的导入导出极大简化,但是对于一些负载的表格形式&…...
PDF转word转ppt软件
下载地址:PDF转word转ppt软件.zip 平时工作生活经常要用到PDF转word转ppt软件,电脑自带的又要开会员啥的很麻烦,现在分享这款软件直接激活就可以免费使用了,超级好用,喜欢的可以下载...
如何评价2023年第八届数维杯数学建模ABC题?
2024年第九届数维杯大学生数学建模挑战赛将于北京时间2024年5月10日08:00至5月13日09:00举行,竞赛倒计时17天,近期准备参加的同学还是很迷茫,不知道如何选题解题,今天整理数维杯选题策略,这里也预祝同学们在竞赛中取得好成绩! 竞赛特点 数维杯大学生数学建模挑战赛每年分…...
CentOS 7 :虚拟机网络环境配置+ 安装gcc(新手进)
虚拟机安装完centos的系统却发现无法正常联网,咋破! 几个简单的步骤: 一、检查和设置虚拟机网络适配器 这里笔者使用的桥接模式,朋友们可以有不同的选项设置 二、查看宿主机的网络 以笔者的为例,宿主机采用wlan上网模…...
智慧法治:AI技术如何赋能法律行业创新
🧑 作者简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…...
K-RTD01和利时FW248中控卡件
K-RTD01和利时FW248中控卡件。 系统概述 的全称为保护工程师站及录波分析后台”是利用现代计算机和网络技术,K-RTD01和利时FW248中控卡件。实时收集变电站运行和故障信息,并通过对变电站的故障信息进行综合分析,K-RTD01和利时FW248中控卡件。…...
[蓝桥杯]真题讲解:合并数列(双指针+贪心)
[蓝桥杯]真题讲解:班级活动(贪心) 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解:合并数列(双指针贪心) 二、正解代码 1、C #include<bits/stdc.h> #define in…...
科林Linux_4 信号
#include <signal.h> 信号signal:Linux或Unix系统支持的经典的消息机制,用于处置进程,挂起进程或杀死进程 kill -l #查看系统支持的信号 1~31 Unix经典信号(软件开发工程师) 32、33信号被系统隐藏…...
C++:map和set类
关联式容器 在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、 forward_list(C11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面 存储的是元素本身。那什么是关…...
[C/C++] -- 代理模式
代理模式是一种结构型设计模式,允许一个对象(代理)控制另一个对象的访问。代理对象通常充当客户端和实际目标对象之间的中间人,从而控制对目标对象的访问,可以在访问前后进行一些额外的处理。 代理模式的优点包括&…...
电商平台遭遇DDOS、CC攻击有什么防护方案
电商平台遭遇DDOS、CC攻击有什么防护方案?在数字化浪潮的推动下,电商平台已成为现代商业的重要组成部分,为消费者提供便捷、多样的购物体验。然而,随着业务的发展,电商平台也面临着日益严峻的网络安全挑战,…...
什么是 IIS
什么是 IIS 一、什么是 IIS二、IIS 的功能三、IIS 几点说明四、IIS 的版本五、IIS 常见的组合 欢迎关注【云边小网安】 一、什么是 IIS IIS:指 Internet Information Services ,是一种由微软公司开发的 Web 服务器应用程序。IIS:是一种 Web …...
京东页面(黏性定位的实现)
前言: 本文章将分享一些我这周在制作京东页面的实现部分,页面表面大体和京东页面差不多,在里面加了一点script,但是很容易理解,希望大家看到可以有所收获,如果我有哪部分写的不太好,欢迎大家来跟我交流! 🥰个人主页:心.c 🥳文章专题:京东页面制作 &#…...
【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第6章 安装Samba
RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…...
json-server 模拟接口服务
前端开发经常需要模拟接口请求,可以通过 json-server 实现。 1. 安装 json-server 在前端项目的终端命令行中执行 npm i json-server2. 创建数据源 在项目中新建文件 db.json ,与 package.json 同级,内容为模拟的数据 注意 json 文件对格式…...
pycharm 将项目连同库一起打包及虚拟环境的使用
目录 一、创建虚拟环境 1、用 anaconda 创建 2、Pycharm 直接创建 二、虚拟环境安装第三方库 1、创建项目后,启动终端(Alt F12),或者点击下方标记处。 2、使用 pip 或者 conda 来进行三方库的安装或卸载 3、将项目中的库放入文档,便于…...
GO语言核心30讲 实战与应用 (第三部分)
原站地址:Go语言核心36讲_Golang_Go语言-极客时间 一、io包中的接口和工具 1. strings.Builder、strings.Reader 和 bytes.Buffer 这些类型实现了 io 包的很多接口,目的是什么? 是为了提高不同程序实体之间的互操作性。 程序实体是指比如网…...
HttpServletRequest对象
1.作用 主要作用是用来接收客户端发送过来的请求信息,由servlet容器封装好传递给service()方法 2.常用方法 常用方法描述StringBuffer getRequestURL()获取客户端发送请求时的完整URLString getRequestURI()获取请求行中的资源名称部分(项目名称开始&…...
Linux入门攻坚——23、DNS和BIND基础入门1
DNS——Domain Name Service,协议(C/S,53/udp,53/tcp) BIND——Berkeley Internet Name Domain,ISC(www.isc.org) 互联网络上主机之间的通信依靠的是IP,而人或程序一般使…...
springboot中mybatisplus注意事项
使用代码生成工具CodeGenerator 需要修改的内容 dsc.setUsername(“root”); mysql账号dsc.setPassword(“root”); mysql密码strategy.setInclude(“crm_edu”); 表名pc.setModuleName(“eduservice”); //模块名 package com.test.demo;import com.baomidou.mybatisplus.a…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
