后端常用技能:基于easy-poi实现excel一对多、多对多导入导出【附带源码】

news2024/12/28 11:02:30

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1653371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python作业五

题目&#xff1a;注册登录 制作一个注册登录模块 注册&#xff1a;将用户填入的账户和密码保存到一个文件(users.bin) 登陆&#xff1a;将用户填入账户密码和users.bin中保存的账户密码进行比对,如果账户和密码完全相同 那 么登录成功&#xff0c;否则登录失败…

Windows 驱动程序自签名流程

Windows 官方案例https://github.com/microsoft/Windows-driver-samples。编译通过了&#xff0c;都是Test Sign。这里找到一种自签名的方法&#xff0c;记录一下。文章来源https://konata.tech/2020/10/16/signWindowsDriver/ 创建自签名证书 从 Windows PowerShell 3.0 版本…

Spring事件

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Spring⛺️稳中求进&#xff0c;晒太阳 Spring事件 简洁 Spring Event&#xff08;Application Event&#xff09;就是一个观察者模式&#xff0c;一个bean处理完任务后希望通知其他Bean的…

QGIS编译

一&#xff0c;安装&#xff1a;OSGeo4W 二&#xff0c;安装&#xff1a;Cygwin64 https://www.cygwin.com/setup-x86_64.exe 三&#xff0c;安装&#xff1a; 安装bison和flex 四&#xff09;QGIS_3.28 下载QGIS_3.28的源码包 五 环境变量设置&#xff1a; echo off set VS19…

基于springboot实现校园失物招领系统【项目源码+论文说明】

基于springboot实现校园失物招领系统演示 摘要 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化&#xff0c;身边经常有同学丢失了东西或者衣服而烦恼&#xff0c;为了找到自己心爱的物品疲于奔命&#xff0c;还不…

使用电路仿真软件教学的优势分析

随着科技的飞速发展&#xff0c;电子工程领域对人才的需求与日俱增。为了满足这一需求&#xff0c;教育者们不断探索着更加高效、直观的教学方法。电路仿真软件的出现&#xff0c;为电子工程教学注入了新的活力&#xff0c;它以其独特的优势&#xff0c;成为现代电子工程教育中…

YOLO v6 iou_loss dfl_loss一直为0

Question img record infomation path is:…/mydata/images.train_cache.json Train: Final numbers of valid images: 1248/ labels: 1248. 0.1s for dataset initialization. img record infomation path is:…/mydata/images.val_cache.json Convert to COCO format 100%|█…

Go微服务精讲:Go-Zero全流程实战即时通讯(超清)

go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性&#xff0c;经受了充分的实战检验。 Go微服务精讲&#xff1a;Go-Zero全流程实战即时通讯(超清) go-zero 中的 api&#xff0c;rpc&#xff0c;数据库等涉及的代码&#xff0c;…

Win10子系统wsl开机自启动jar包(nginx、redis、apache2、ssh等)

一. 需求背景 最近在研究nextcloud项目,选择了Win10子系统方案,而win10子系统wsl是通过接口实现的,很多linux上的常规操作在wsl上无法实现,比如配置开机自启动jar包以及其他依赖程序。只能通过windows系统重启时,自动执行脚本,去实现开机自启linux中的相关程序。 二. wsl…

我独自升级崛起怎么下载 游戏下载教程分享

《我独自升级&#xff1a;崛起》这款游戏核心聚焦于激烈的战斗与角色的持续成长。新加入的玩家首要任务是熟悉基础攻击模式&#xff0c;随后深入探索技能组合策略与连贯招式的艺术&#xff0c;同时掌握防守与躲避技巧&#xff0c;这些都是战斗中不可或缺的关键。随着战斗的持续…

Quartz怎么简单创建一个定时执行的任务

1.安装Quartz包 2.编写Job任务 继承 IJob编辑自定义任务 3.调用job&#xff0c;以指定时间策略执行 定时600s执行一次 StdSchedulerFactory factory new StdSchedulerFactory(); IScheduler scheduler await factory.GetScheduler(); await scheduler.Start();// 定义…

数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面

项目目录的管理 前言 在上几篇的文章里面我们完成了基本环境的搭建,整理了项目数据库表结构并且成功的手动的加载了Qt的mysql数据库驱动&#xff0c;现在就要开始完成项目准备工作的最后一步:构建项目目录,一个好的项目离不开一个好的代码组织结构,所以在开始动手写我们这个项…

环形链表(成环的第一个相交的节点在哪)(中等)(证明题)

环形链表&#xff08;成环的相交的节点在哪&#xff09;&#xff08;中等&#xff09;&#xff08;证明题&#xff09; 142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题目 解题思路&#xff08;证明一下&#xff09; 这里的关键点在于你需要证明LT 然后就可以解题…

excel中图片url转为jpg

步骤 打开Excel&#xff0c;并按下 Alt F11 打开VBA编辑器。在VBA编辑器中&#xff0c;插入一个新的模块&#xff08;右键点击项目资源管理器中的模块 -> 插入 -> 模块&#xff09;。在新模块的代码窗口中&#xff0c;复制并粘贴以下示例代码。根据需要修改代码中的变量…

Twitch赠送暗区突围测试资格 超简单暗区突围测试资格领取教程

作为直播界的领航者&#xff0c;Twitch平台不仅是全球游戏文化直播的中心舞台&#xff0c;更是频繁联袂各路游戏大作&#xff0c;为粉丝们奉上别具匠心的互动盛宴&#xff0c;让观赛的同时解锁诱人的游戏内惊喜。正值《暗区突围》PC版测试的热潮涌动&#xff0c;Twitch乘势加强…

uniapp + vue3 使用axios

场景 uniapp自带的uni.request不太好用&#xff0c;也有可能是自己用axios用的太熟悉了&#xff0c;所以还是用axios趁手点&#xff0c;所以尝试在uniapp中使用axios。 操作 因为uniapp项目没有package.json&#xff0c;所以先在项目根目录下执行 npm init, 执行完毕后直接…

案例|200多套设备实时监测,守护江西彰湖水库安全

中型水库作为水利建设的重要组成部分&#xff0c;在防洪、供水、农业灌溉、改善民生和生态效益等方面都具有重要意义。国务院发布《关于切实加强水库除险加固和运行管护工作的通知》&#xff0c;重点提出要提升信息化管理能力&#xff0c;要加快建设水库雨水情测报、大坝安全监…

【RAG 论文】Dense X 检索:将“命题”作为检索粒度

论文&#xff1a;Dense X Retrieval: What Retrieval Granularity Should We Use? ⭐⭐⭐⭐ Code: github.com/ct123098/factoid-wiki 文章目录 一、论文速读二、命题&#xff08;Proposition&#xff09;三、FactoidWiki四、实验及分析4.1 Passage Retrieval 任务4.2 Open-Do…

C++ | Leetcode C++题解之第69题x的平方根

题目&#xff1a; 题解&#xff1a; class Solution { public:int mySqrt(int x) {if (x 0) {return 0;}double C x, x0 x;while (true) {double xi 0.5 * (x0 C / x0);if (fabs(x0 - xi) < 1e-7) {break;}x0 xi;}return int(x0);} };

前端高频面试题 5.08

事件委托 事件委托是前端开发中常用的一种优化性能和代码可维护性的方法&#xff0c;它基于DOM的事件冒泡机制。当一个元素触发事件时&#xff0c;这个事件会按照从顶层到底层的顺序传播&#xff0c;直到最底层的元素&#xff08;通常是文档的根节点&#xff09;。事件委托利用…