SpringBoot 集成 EasyExcel 3.x 实现 Excel 导出

目录

 

EasyExcel官方文档

EasyExcel是什么?

EasyExcel注解

 springboot集成EasyExcel

简单入门导出 :

实体类

 自定义转换类

测试一下

复杂表头一对多导出 : 

 自定义注解

定义实体类

自定义单元格合并策略

 测试一下

 


 

EasyExcel官方文档

EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel

EasyExcel是什么?

        EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具.他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。

EasyExcel注解

  • @ExcelProperty: 核心注解,value属性可用来设置表头名称,converter属性可以用来设置类型转换器

  • @ColumnWidth: 用于设置表格列的宽度

  • @DateTimeFormat: 用于设置日期转换格式

  • @NumberFormat: 用于设置数字转换格式

  • @ExcelIgnore:默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

  • @ExcelIgnoreUnannotated:默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与读写

具体使用请参考:

读Excel:读Excel | Easy Excel

写Excel:写Excel | Easy Excel

 springboot集成EasyExcel

        springboot集成EasyExcel3.x非常简单,只需要引入以下依赖即可。

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.3</version>
</dependency>

简单入门导出 :

        接下来使用EasyExcel实现简单导出功能。

实体类

       导入数据还是导出数据都可以想象成具体某个对象的集合。

@Data
public class StudentDo {@ExcelProperty("用户编号")@ColumnWidth(20)private Long id;@ExcelProperty("用户名")@ColumnWidth(20)private String studentName;@ExcelIgnoreprivate String password;@ExcelProperty("生日")@ColumnWidth(20)@DateTimeFormat("yyyy-MM-dd")private Date birthday;@ExcelProperty("身高(米)")@NumberFormat("#.##")@ColumnWidth(20)private Double height;@ExcelProperty(value = "性别", converter = GenderConverter.class)@ColumnWidth(10)private Integer gender;
}

 自定义转换类

        导出excel数据时,可能会遇到XXX状态、XXX类型和性别等需要转换的字段,例如:0->男,1->女。需实现Converter接口来自定义转换器。
 

orElse(null)与orElseGet(null)区别:

orElse(null)表示如果一个都没找到返回null。【orElse()中可以塞默认值。如果找不到就会返回orElse中你自己设置的默认值。】

orElseGet(null)表示如果一个都没找到返回null。【orElseGet()中可以塞默认值。如果找不到就会返回orElseGet中你自己设置的默认值。】

区别就是在使用方法时,即时时没有值 也会执行 orElse 内的方法, 而 orElseGet则不会。

Enum:

@Getter
@AllArgsConstructor
public enum GenderEnum {/*** 男性*/MALE(0, "男性"),/*** 女性*/FEMALE(1, "女性"),/*** 未知*/UNKNOWN(2, "未知");private final Integer value;@JsonFormatprivate final String description;public static GenderEnum convert(Integer value) {return Stream.of(values()).filter(bean -> bean.value.equals(value)).findAny().orElse(UNKNOWN);}public static GenderEnum convert(String description) {return Stream.of(values()).filter(bean -> bean.description.equals(description)).findAny().orElse(UNKNOWN);}
}
Converter:
public class GenderConverter implements Converter<Integer> {@Overridepublic Class<?> supportJavaTypeKey() {return Integer.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic Integer convertToJavaData(ReadConverterContext<?> context) {return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue();}@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) {return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());}
}

测试一下

File.separator等价于“\”

package com.springwork.high.easyexcel.controller;/*** @author gj* @version 1.0.0* @date 2023/7/7 15:36*/import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springwork.high.easyexcel.vo.StudentDo;
import com.sun.deploy.net.URLEncoder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;/*** EasyExcel导入导出** @author william@StarImmortal*/
@RestController
@RequestMapping("/excel")
public class ExcelController {@GetMapping("/export/student")public void exportUserExcel(HttpServletResponse response) {try {this.setExcelResponseProp(response, "学生列表");List<StudentDo> studentList = this.getStudentList();EasyExcel.write(response.getOutputStream()).head(StudentDo.class).excelType(ExcelTypeEnum.XLSX).sheet("学生列表").doWrite(studentList);} catch (IOException e) {throw new RuntimeException(e);}}/*** 设置响应结果** @param response    响应结果对象* @param rawFileName 文件名* @throws UnsupportedEncodingException 不支持编码异常*/private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("%20", File.separator+"+");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");}/*** 读取学生列表数据** @return 学生数据* @throws IOException IO异常*/private List<StudentDo> getStudentList() throws IOException {ObjectMapper objectMapper = new ObjectMapper();ClassPathResource classPathResource = new ClassPathResource("data/students.json");InputStream inputStream = classPathResource.getInputStream();return objectMapper.readValue(inputStream, new TypeReference<List<StudentDo>>() {});}
}

json:将JSON数据放在resource/data下即可

[{"id": 1,"studentName": "张三","password": "1234","birthday": "2000-01-01","height": "170.5","gender": 0},{"id": 2,"studentName": "李四","password": "1234","birthday": "2000-01-02","height": "175.5","gender": 1},{"id": 3,"studentName": "王五","password": "1234","birthday": "2000-01-03","height": "180.5","gender": 1},{"id": 4,"studentName": "赵六","password": "1234","birthday": "2000-01-04","height": "176.5","gender": 0}
]

测试:

使用postman进行测试,选择“Send and Download”选项。

dbf2cb089549448e835b234d113f6e0e.png

 

复杂表头一对多导出 : 

        由于 EasyPoi 支持嵌套对象导出,直接使用内置 @ExcelCollection 注解即可实现,但是 EasyExcel 不支持一对多导出,只能自行实现,可以通过自定义合并策略方式来实现一对多导出。

ba96bd1f013348e48e73a9104da091f0.png

 自定义注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelMerge {/*** 是否合并单元格** @return true || false*/boolean merge() default true;/*** 是否为主键(即该字段相同的行合并)** @return true || false*/boolean isPrimaryKey() default false;
}

定义实体类

        需要合并的字段上标明@ExcelMerge

        二级表头通过@ExcelProperty(value = {"学生信息","学生编号"})设置

@Data
public class SchoolDo {@ExcelProperty("学校编号")@ColumnWidth(20)@ExcelMerge(merge = true,isPrimaryKey = true)private String schoolId;@ExcelProperty("学校名")@ColumnWidth(20)@ExcelMerge(merge = true)private String schoolName;@ExcelProperty(value = {"学生信息","学生编号"})@ColumnWidth(20)private Long studentId;@ExcelProperty(value = {"学生信息","学生名"})@ColumnWidth(20)private String studentName;@ExcelIgnoreprivate String password;@ExcelProperty(value = {"学生信息","生日"})@ColumnWidth(20)@DateTimeFormat("yyyy-MM-dd")private Date birthday;@ExcelProperty(value = {"学生信息","身高(米)"})@NumberFormat("#.##")@ColumnWidth(20)private Double height;@ExcelProperty(value = {"学生信息","性别"},converter = GenderConverter.class)@ColumnWidth(10)private Integer gender;
}

自定义单元格合并策略

        当 Excel 中两列主键相同时,合并被标记需要合并的列,将自定义合并策略 ExcelMergeStrategy 通过 registerWriteHandler 注册上去:


public class ExcelMergeStrategy implements RowWriteHandler {/*** 主键下标*/private Integer primaryKeyIndex;/*** 需要合并的列的下标集合*/private final List<Integer> mergeColumnIndexList = new ArrayList<>();/*** 数据类型*/private final Class<?> elementType;public ExcelMergeStrategy(Class<?> elementType) {this.elementType = elementType;}@Overridepublic void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {// 判断是否为标题if (isHead) {return;}// 获取当前工作表Sheet sheet = writeSheetHolder.getSheet();// 初始化主键下标和需要合并字段的下标if (primaryKeyIndex == null) {this.initPrimaryIndexAndMergeIndex(writeSheetHolder);}// 判断是否需要和上一行进行合并// 不能和标题合并,只能数据行之间合并if (row.getRowNum() <= 1) {return;}// 获取上一行数据Row lastRow = sheet.getRow(row.getRowNum() - 1);// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并if (lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue())) {for (Integer mergeIndex : mergeColumnIndexList) {CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(), mergeIndex, mergeIndex);sheet.addMergedRegionUnsafe(cellRangeAddress);}}}/*** 初始化主键下标和需要合并字段的下标** @param writeSheetHolder WriteSheetHolder*/private void initPrimaryIndexAndMergeIndex(WriteSheetHolder writeSheetHolder) {// 获取当前工作表Sheet sheet = writeSheetHolder.getSheet();// 获取标题行Row titleRow = sheet.getRow(0);// 获取所有属性字段Field[] fields = this.elementType.getDeclaredFields();// 遍历所有字段for (Field field : fields) {// 获取@ExcelProperty注解,用于获取该字段对应列的下标ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);// 判断是否为空if (null == excelProperty) {continue;}// 获取自定义注解,用于合并单元格ExcelMerge excelMerge = field.getAnnotation(ExcelMerge.class);// 判断是否需要合并if (null == excelMerge) {continue;}for (int i = 0; i < fields.length; i++) {Cell cell = titleRow.getCell(i);if (null == cell) {continue;}// 将字段和表头匹配上if (excelProperty.value()[0].equalsIgnoreCase(cell.getStringCellValue())) {if (excelMerge.isPrimaryKey()) {primaryKeyIndex = i;}if (excelMerge.merge()) {mergeColumnIndexList.add(i);}}}}// 没有指定主键,则异常if (null == this.primaryKeyIndex) {throw new IllegalStateException("使用@ExcelMerge注解必须指定主键");}}
}

 测试一下

 @GetMapping("/export/school")public void exportOrderExcel(HttpServletResponse response) {try {this.setExcelResponseProp(response, "学校列表");List<SchoolDo> exportData = this.getSchoolList();EasyExcel.write(response.getOutputStream()).head(SchoolDo.class).registerWriteHandler(new ExcelMergeStrategy(SchoolDo.class)).excelType(ExcelTypeEnum.XLSX).sheet("学校列表").doWrite(exportData);} catch (IOException e) {throw new RuntimeException(e);}}
/*** 读取学校列表数据** @return 列表数据* @throws IOException IO异常*/private List<SchoolDo> getSchoolList() throws IOException {ObjectMapper objectMapper = new ObjectMapper();ClassPathResource classPathResource = new ClassPathResource("data/schools.json");InputStream inputStream = classPathResource.getInputStream();return objectMapper.readValue(inputStream, new TypeReference<List<SchoolDo>>() {});}

json数据:

[{"schoolId":"1","schoolName":"北京大学","studentId": 1,"studentName": "张三","password": "1234","birthday": "2000-01-01","height": "170.5","gender": 0},{"schoolId":"1","schoolName":"北京大学","studentId": 2,"studentName": "李四","password": "1234","birthday": "2000-01-02","height": "175.5","gender": 1},{"schoolId":"1","schoolName":"北京大学","studentId": 3,"studentName": "王五","password": "1234","birthday": "2000-01-03","height": "180.5","gender": 1},{"schoolId":"1","schoolName":"北京大学","studentId": 4,"studentName": "赵六","password": "1234","birthday": "2000-01-04","height": "176.5","gender": 0},{"schoolId":"2","schoolName":"河北大学","studentId": 1,"studentName": "张三","password": "1234","birthday": "2000-01-01","height": "170.5","gender": 0},{"schoolId":"2","schoolName":"河北大学","studentId": 2,"studentName": "李四","password": "1234","birthday": "2000-01-02","height": "175.5","gender": 1},{"schoolId":"2","schoolName":"河北大学","studentId": 3,"studentName": "王五","password": "1234","birthday": "2000-01-03","height": "180.5","gender": 1},{"schoolId":"2","schoolName":"河北大学","studentId": 4,"studentName": "赵六","password": "1234","birthday": "2000-01-04","height": "176.5","gender": 0}
]

测试:

使用postman进行测试,选择“Send and Download”选项。

c0439545ac76411cb6ee32a948c107d1.png

参考文献

官方文档:

 

https://www.yuque.com/easyexcel/doc/easyexcel

一对多导出优雅方案:

 

https://github.com/alibaba/easyexcel/issues

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

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

相关文章

SpringCloud学习路线(5)—— Nacos配置管理

一、统一配置管理 需求&#xff1a; 微服务配置能实现统一的管理&#xff0c;比如希望改动多个配置&#xff0c;但不希望逐个配置&#xff0c;而是在一个位置中改动&#xff0c;并且服务不用重启即用&#xff08;热更新&#xff09;。 &#xff08;一&#xff09;使用配置管理…

AN OVERVIEW OF LANGUAGE MODELS RECENT DEVELOPMENTS AND OUTLOOK

LLM系列相关文章&#xff0c;针对《AN OVERVIEW OF LANGUAGE MODELS: RECENT DEVELOPMENTS AND OUTLOOK》的翻译。 语言模型综述&#xff1a;近年来的发展与展望 摘要1 引言2 语言模型的类型2.1 结构化LM2.2 双向LM2.3 置换LM 3 语言单元3.1 字符3.2 单词和子单词3.2.1 基于统…

十八、Unity游戏引擎入门

1、下载 首先需要下载Unity Hub,下载网址:https://unity.com/cn。 然后在其中下载Unity编辑器并安装,可选择最新版本。 接着需要选择适合的开发环境,例如Android Studio或Xcode,以便进行手机游戏开发。在安装完Unity后,需要根据项目需求下载对应的模块和插件…

CRM排名前三的的系统有哪些特点?

crm经过多年的发展&#xff0c;不仅可以管理好客户关系还是企业重要的战略武器。让企业的销售、市场营销和客服服务部门建立密切联系&#xff0c;在crm一个平台上处理商机&#xff0c;简化业务流程&#xff0c;为组织降本增效。国内crm系统排名哪些技术商更靠前&#xff1f; 1…

微服务sleuth+zipkin——链路追踪

一、链路追踪&#x1f349; 1.什么是链路追踪&#xff1f;&#x1f95d; 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多模块。这些模块负责不同的功能&#xff0c;组合成系统&#xff0c;最终可以提供丰富的功能。在这种架构中&#xff0c;一次请求往往需要…

vue或react当中canvas实现电子签名组件和使用canvas进行图片压缩

<template><div><h1>vue3</h1><canvas id"canvasWrite"> 浏览器不支持Canvas,请升级浏览器 </canvas><div><button class"submit" click"submitWrite">提交签名</button><button clas…

vscode debug的方式

在.vscode文件夹下建立launch.json 例子1&#xff1a;调试python 来自 https://github.com/chunleili/tiPBD/tree/amg {"version": "0.2.0","configurations": [{"name": "hpbd 5 5","type": "python&quo…

Java开发中使用sql简化开发

引语&#xff1a; 在Java开发中&#xff0c;我们更希望数据库能直接给我们必要的数据&#xff0c;然后在业务层面直接进行使用&#xff0c;所以写一个简单的sql语句有助于提高Java开发效率&#xff0c;本文由简单到复杂的小白吸收&#xff0c;还请多多指教。 使用MySQL数据库…

Stable Diffusion学习笔记

一些零散笔记 灰常好的模型网站 LiblibAI哩布哩布AI-中国领先原创AI模型分享社区 出图效率倍增&#xff01;47个高质量的 Stable Diffusion 常用模型推荐 - 优设网 - 学设计上优设 关键词Prompt顺序 画质 风格 主体 外表、描述 表情、情绪 姿势 背景 杂项 同时可以…

23家企业推出昇腾AI系列新品 覆盖云、边、端智能硬件

[中国&#xff0c;上海&#xff0c;2023年7月6日] 昇腾人工智能产业高峰论坛在上海举办。论坛现场&#xff0c;大模型联合创新启动&#xff0c;26家行业领军企业、科研院所与华为将共同基于昇腾AI进行基础大模型与行业大模型应用创新。同时&#xff0c;华为携手伙伴联合发布昇腾…

【C++进阶之路】list的基本使用和模拟实现

文章目录 初步认识①定义②底层原理③迭代器的分类 一、基本使用1.插入结点元素2.删除结点元素3.合并两个有序链表4.将一条链表的某一部分转移到另一条链表5.对链表排序并去重6.vector与list排序的比较 二、模拟实现①要点说明②基本框架③迭代器构造函数- -*->list里的迭代…

Raft算法之日志复制

Raft算法之日志复制 一、日志复制大致流程 在Leader选举过程中&#xff0c;集群最终会选举出一个Leader节点&#xff0c;而集群中剩余的其他节点将会成为Follower节点。Leader节点除了向Follower节点发送心跳消息&#xff0c;还会处理客户端的请求&#xff0c;并将客户端的更…

ElasticSearch搜索相关性及打分的相关原理

文章目录 一、相关性和打分简介二、TF-IDF得分计算公式三、BM25&#xff08;Best Matching 25&#xff09;四、使用explain查看TF-IDF五、通过Boosting控制相关度 一、相关性和打分简介 举个例子来说明&#xff1a; 假设有一个电商网站&#xff0c;用户在搜索框中输入了关键词&…

理解LLM中的ReAct

large language models (LLMs)大语言模型在语义理解和交互式决策方面有着不错的表现。ReAct在一次交互中循环使用推理和行动两个操作解决复杂问题&#xff0c;推理即利用模型自身语义理解能力&#xff0c;行动则利用模型以外的能力&#xff08;如计算、搜索最新消息&#xff0c…

架构训练营学习笔记:4-2 存储架构模式之复制架构

高可用的关键指标 问题&#xff1a;分为故障跟灾难。不是有了多活架构就不在用复制架构 &#xff0c;还是之前的合适原则&#xff1a;多活架构的技术复杂度 跟成本都比复制架构高。 高可用的关键指标 恢复时间目标(RecoveryTimeObjective&#xff0c;RTO)指为避免在灾难发生后…

Spring Cloud Gateway - 新一代微服务API网关

Spring Cloud Gateway - 新一代微服务API网关 文章目录 Spring Cloud Gateway - 新一代微服务API网关1.网关介绍2.Spring Cloud Gateway介绍3.Spring Cloud Gateway的特性4.Spring Cloud Gateway的三大核心概念5.Gateway工作流程6.Gateway核心配置7.动态路由8.Predicate自定义P…

阿里云RockMQ与SpringBoot的整合

前言&#xff1a; 开源版本Rocket和商业版本的RocketMQ有些不同&#xff0c;研究的是商业版本的RocketMQ&#xff0c;阿里云的官方文档&#xff0c;感觉有点乱。看不咋明白&#xff0c;网上虽然有教程&#xff0c;大都还是有点缺少&#xff0c;有时候会突然跳了步骤&#xff0c…

C# 细说async/await的用法

目录 一&#xff0c;引言 二&#xff0c;实例演示 2.1 多线程同步执行下载任务&#xff0c;任务完成后通知 2.2 异步执行下载任务&#xff0c;任务完成后通知 三&#xff0c;async/await的用法 3.1 跨线程修改UI控件 3.2 异步获取数据 一&#xff0c;引言 首先先来区分…

网上书店管理系统

目录 一、系统需求分析 二、数据库概念结构设计 四、数据库物理实现 五、数据库功能调试 一、系统需求分析 需求概述 1.系统背景 当今互联网的迅速发展&#xff0c;使得人们获取信息变得极其便利。在从前&#xff0c;人们以线下书店购买书籍的方式获取知识&#xff0c;常常…

WEB:Confusion1

背景知识 SSTI漏洞 题目 根据网站图片和题目描述的提示&#xff0c;大象是php&#xff0c;蟒蛇是python&#xff0c;说明了这个网站是用python写的 在python中&#xff0c;比较常规的漏洞就是SSTI模板注入 没有思路&#xff0c;先点login和register页面看看 查看源代码 之前…