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

Android Ant编译环境配置(Win)
1、 载ant包: 2、设置环境变量: 3、检查是否设置成功及版本 4、执行命令: android update project -p . -n “projectname”(例如:android update project --target 1 -p . -n “Couplet”)(只输入红色部分也是可以的…...

条件变量解决同步问题之打印金鱼
说明 本代码为jyy老师上课演示条件变量解决同步问题示例(本人只做记录与分享) 本人未使用老师封装的POSIX线程库, 直接在单文件中调试并注释 问题描述 有三类线程 T1 若干: 死循环打印< T2 若干: 死循环打印> T3 若干: 死循环打印_ 任务: 对线程同步,使得屏幕…...

10分钟了解Golang泛型
泛型是Golang在1.18版本引入的强大工具,能够帮助我们在合适的场合实现简洁、可读、可维护的代码。原文: Go Generics: Everything You Need To Know 导言 可能有人会觉得Go泛型很难,因此想要借鉴其他语言(比如Java、NodeJS)的泛型…...

鸿蒙内核源码分析(Shell解析篇) | 应用窥视内核的窗口
系列篇从内核视角用一句话概括shell的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell进程都会再创建两个任务。 客户端任务(ShellEntry): 负责接受来自终端(控制台)敲入的一个个字符&…...

TypeScript在前端项目的渐进式采用策略
渐进式采用 TypeScript 在前端项目中的策略通常包括: 引入TypeScript 如果我们有一个简单的JavaScript模块utils.js,它包含一个函数用于计算两数之和: // utils.js export function add(a, b) {return a b; }首先,我们将文件扩展名改为.t…...

C++容器常用集合(附传送门)
C常用的容器: string容器 C容器——string-CSDN博客 储存字符串的 vector容器 C容器——vector-CSDN博客 向量是动态数组,可以自动扩展以容纳更多元素。 插入和删除元素的时间复杂度取决于操作的位置 tuple容器(元组) C容器…...

基于springboot的校园资料分享平台源码数据库
基于springboot的校园资料分享平台源码数据库 随着信息互联网购物的飞速发展,国内放开了自媒体的政策,一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求,创…...
卷积神经网络(CNN)
大家好,这里是七七,今天来更新关于CNN相关的内容同了。本文是针对CNN原理的说明,但对于小白不是非常友好,建议先掌握神经网络相应知识再进行阅读哦。 一、卷积与互相关 卷积 卷积运算是对两个函数进行的一种数学运算,…...

Linux入门攻坚——22、通信安全基础知识及openssl、CA证书
Linux系统常用的加解密工具:OpenSSL,gpg(是pgp的实现) 加密算法和协议: 对称加密:加解密使用同一个秘钥; DES:Data Encryption Standard,数据加密标准&…...

无障碍Web开发:遵循WCAG标准构建包容性用户体验
无障碍Web开发旨在确保所有用户,无论其身体条件或能力如何,都能轻松、有效地访问和使用Web内容。遵循Web Content Accessibility Guidelines (WCAG) 标准是实现这一目标的关键。以下是一些基于WCAG标准的无障碍Web开发实践,以构建更具包容性的…...