当前位置: 首页 > news >正文

后端常用技能:基于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. 引言 在业务系统开发中&#xff0c;我们经常遇到excel导入导出的业务场景&#xff0c;普通的excel导入导出我们可以利用 apache poi、jxl以及阿里开源的easyexcel来实现&#xff0c;特别easyexcel更是将excel的导入导出极大简化&#xff0c;但是对于一些负载的表格形式&…...

PDF转word转ppt软件

下载地址&#xff1a;PDF转word转ppt软件.zip 平时工作生活经常要用到PDF转word转ppt软件&#xff0c;电脑自带的又要开会员啥的很麻烦&#xff0c;现在分享这款软件直接激活就可以免费使用了&#xff0c;超级好用&#xff0c;喜欢的可以下载...

如何评价2023年第八届数维杯数学建模ABC题?

2024年第九届数维杯大学生数学建模挑战赛将于北京时间2024年5月10日08:00至5月13日09:00举行,竞赛倒计时17天,近期准备参加的同学还是很迷茫,不知道如何选题解题,今天整理数维杯选题策略,这里也预祝同学们在竞赛中取得好成绩! 竞赛特点 数维杯大学生数学建模挑战赛每年分…...

CentOS 7 :虚拟机网络环境配置+ 安装gcc(新手进)

虚拟机安装完centos的系统却发现无法正常联网&#xff0c;咋破&#xff01; 几个简单的步骤&#xff1a; 一、检查和设置虚拟机网络适配器 这里笔者使用的桥接模式&#xff0c;朋友们可以有不同的选项设置 二、查看宿主机的网络 以笔者的为例&#xff0c;宿主机采用wlan上网模…...

智慧法治:AI技术如何赋能法律行业创新

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…...

K-RTD01和利时FW248中控卡件

K-RTD01和利时FW248中控卡件。 系统概述 的全称为保护工程师站及录波分析后台”是利用现代计算机和网络技术&#xff0c;K-RTD01和利时FW248中控卡件。实时收集变电站运行和故障信息&#xff0c;并通过对变电站的故障信息进行综合分析&#xff0c;K-RTD01和利时FW248中控卡件。…...

[蓝桥杯]真题讲解:合并数列(双指针+贪心)

[蓝桥杯]真题讲解&#xff1a;班级活动&#xff08;贪心&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;合并数列&#xff08;双指针贪心&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define in…...

科林Linux_4 信号

#include <signal.h> 信号signal&#xff1a;Linux或Unix系统支持的经典的消息机制&#xff0c;用于处置进程&#xff0c;挂起进程或杀死进程 kill -l #查看系统支持的信号 1~31 Unix经典信号&#xff08;软件开发工程师&#xff09; 32、33信号被系统隐藏&#xf…...

C++:map和set类

关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、 forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面 存储的是元素本身。那什么是关…...

[C/C++] -- 代理模式

代理模式是一种结构型设计模式&#xff0c;允许一个对象&#xff08;代理&#xff09;控制另一个对象的访问。代理对象通常充当客户端和实际目标对象之间的中间人&#xff0c;从而控制对目标对象的访问&#xff0c;可以在访问前后进行一些额外的处理。 代理模式的优点包括&…...

电商平台遭遇DDOS、CC攻击有什么防护方案

电商平台遭遇DDOS、CC攻击有什么防护方案&#xff1f;在数字化浪潮的推动下&#xff0c;电商平台已成为现代商业的重要组成部分&#xff0c;为消费者提供便捷、多样的购物体验。然而&#xff0c;随着业务的发展&#xff0c;电商平台也面临着日益严峻的网络安全挑战&#xff0c;…...

什么是 IIS

什么是 IIS 一、什么是 IIS二、IIS 的功能三、IIS 几点说明四、IIS 的版本五、IIS 常见的组合 欢迎关注【云边小网安】 一、什么是 IIS IIS&#xff1a;指 Internet Information Services &#xff0c;是一种由微软公司开发的 Web 服务器应用程序。IIS&#xff1a;是一种 Web …...

京东页面(黏性定位的实现)

前言: 本文章将分享一些我这周在制作京东页面的实现部分,页面表面大体和京东页面差不多,在里面加了一点script,但是很容易理解,希望大家看到可以有所收获,如果我有哪部分写的不太好,欢迎大家来跟我交流! &#x1f970;个人主页:心.c &#x1f973;文章专题:京东页面制作 &#…...

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第6章 安装Samba

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…...

json-server 模拟接口服务

前端开发经常需要模拟接口请求&#xff0c;可以通过 json-server 实现。 1. 安装 json-server 在前端项目的终端命令行中执行 npm i json-server2. 创建数据源 在项目中新建文件 db.json &#xff0c;与 package.json 同级&#xff0c;内容为模拟的数据 注意 json 文件对格式…...

pycharm 将项目连同库一起打包及虚拟环境的使用

目录 一、创建虚拟环境 1、用 anaconda 创建 2、Pycharm 直接创建 二、虚拟环境安装第三方库 1、创建项目后&#xff0c;启动终端(Alt F12)&#xff0c;或者点击下方标记处。 2、使用 pip 或者 conda 来进行三方库的安装或卸载 3、将项目中的库放入文档&#xff0c;便于…...

GO语言核心30讲 实战与应用 (第三​部分)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、io包中的接口和工具 1. strings.Builder、strings.Reader 和 bytes.Buffer 这些类型实现了 io 包的很多接口&#xff0c;目的是什么&#xff1f; 是为了提高不同程序实体之间的互操作性。 程序实体是指比如网…...

HttpServletRequest对象

1.作用 主要作用是用来接收客户端发送过来的请求信息&#xff0c;由servlet容器封装好传递给service()方法 2.常用方法 常用方法描述StringBuffer getRequestURL()获取客户端发送请求时的完整URLString getRequestURI()获取请求行中的资源名称部分&#xff08;项目名称开始&…...

Linux入门攻坚——23、DNS和BIND基础入门1

DNS——Domain Name Service&#xff0c;协议&#xff08;C/S&#xff0c;53/udp&#xff0c;53/tcp&#xff09; BIND——Berkeley Internet Name Domain&#xff0c;ISC&#xff08;www.isc.org&#xff09; 互联网络上主机之间的通信依靠的是IP&#xff0c;而人或程序一般使…...

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、设置环境变量&#xff1a; 3、检查是否设置成功及版本 4、执行命令&#xff1a; android update project -p . -n “projectname”&#xff08;例如&#xff1a;android update project --target 1 -p . -n “Couplet”&#xff09;(只输入红色部分也是可以的…...

条件变量解决同步问题之打印金鱼

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

10分钟了解Golang泛型

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

鸿蒙内核源码分析(Shell解析篇) | 应用窥视内核的窗口

系列篇从内核视角用一句话概括shell的底层实现为&#xff1a;两个任务&#xff0c;三个阶段。其本质是独立进程&#xff0c;因而划到进程管理模块。每次创建shell进程都会再创建两个任务。 客户端任务(ShellEntry)&#xff1a; 负责接受来自终端(控制台)敲入的一个个字符&…...

TypeScript在前端项目的渐进式采用策略

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

C++容器常用集合(附传送门)

C常用的容器&#xff1a; string容器 C容器——string-CSDN博客 储存字符串的 vector容器 C容器——vector-CSDN博客 向量是动态数组&#xff0c;可以自动扩展以容纳更多元素。 插入和删除元素的时间复杂度取决于操作的位置 tuple容器&#xff08;元组&#xff09; C容器…...

基于springboot的校园资料分享平台源码数据库

基于springboot的校园资料分享平台源码数据库 随着信息互联网购物的飞速发展&#xff0c;国内放开了自媒体的政策&#xff0c;一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求&#xff0c;创…...

卷积神经网络(CNN)

大家好&#xff0c;这里是七七&#xff0c;今天来更新关于CNN相关的内容同了。本文是针对CNN原理的说明&#xff0c;但对于小白不是非常友好&#xff0c;建议先掌握神经网络相应知识再进行阅读哦。 一、卷积与互相关 卷积 卷积运算是对两个函数进行的一种数学运算&#xff0c…...

Linux入门攻坚——22、通信安全基础知识及openssl、CA证书

Linux系统常用的加解密工具&#xff1a;OpenSSL&#xff0c;gpg&#xff08;是pgp的实现&#xff09; 加密算法和协议&#xff1a; 对称加密&#xff1a;加解密使用同一个秘钥&#xff1b; DES&#xff1a;Data Encryption Standard&#xff0c;数据加密标准&…...

无障碍Web开发:遵循WCAG标准构建包容性用户体验

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