后端常用技能:基于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…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...