SpringBoot 整合 Excel 轻松实现数据自由导入导出

01、背景介绍

在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。

之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,性能不够好,严重的时候,还会出现内存异常的现象。

今天我给大家推荐一款性能更好的 Excel 导入导出工具:EasyExcel,希望对大家有所帮助!

easyexcel 是阿里开源的一款 Excel导入导出工具,具有处理速度快、占用内存小、使用方便的特点,底层逻辑也是基于 apache poi 进行二次开发的,目前的应用也是非常广!

图片

相比 EasyPoi,EasyExcel 的处理数据性能非常高,读取 75M (46W行25列) 的Excel,仅需使用 64M 内存,耗时 20s,极速模式还可以更快!

图片

废话也不多说了,下面直奔主题!

02、方案实践

在 SpringBoot 项目中集成 EasyExcel 其实非常简单,仅需一个依赖即可。

<!--EasyExcel相关依赖-->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version>
</dependency>

EasyExcel 的导出导入支持两种方式进行处理

  • 第一种是通过实体类注解方式来生成文件和反解析文件数据映射成对象

  • 第二种是通过动态参数化生成文件和反解析文件数据

下面我们以用户信息的导出导入为例,分别介绍两种处理方式。

2.1、简单导出

首先,我们只需要创建一个UserEntity用户实体类,然后添加对应的注解字段即可,示例代码如下:

public class UserWriteEntity {@ExcelProperty(value = "姓名")private String name;@ExcelProperty(value = "年龄")private int age;@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ExcelProperty(value = "操作时间")private Date time;//set、get...
}

然后,使用 EasyExcel 提供的EasyExcel工具类,即可实现文件的导出。

public static void main(String[] args) throws FileNotFoundException {List<UserWriteEntity> dataList = new ArrayList<>();for (int i = 0; i < 10; i++) {UserWriteEntity userEntity = new UserWriteEntity();userEntity.setName("张三" + i);userEntity.setAge(20 + i);userEntity.setTime(new Date(System.currentTimeMillis() + i));dataList.add(userEntity);}//定义文件输出位置FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user1.xlsx"));EasyExcel.write(outputStream, UserWriteEntity.class).sheet("用户信息").doWrite(dataList);
}

运行程序,打开文件内容结果!

图片

2.2、简单导入

这种简单固定表头的 Excel 文件,如果想要读取文件数据,操作也很简单。

以上面的导出文件为例,使用 EasyExcel 提供的EasyExcel工具类,即可来实现文件内容数据的快速读取,示例代码如下:

首先创建读取实体类

/*** 读取实体类*/
public class UserReadEntity {@ExcelProperty(value = "姓名")private String name;/*** 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配*/@ExcelProperty(index = 1)private int age;@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ExcelProperty(value = "操作时间")private Date time;//set、get...
}

然后读取文件数据,并封装到对象里面

public static void main(String[] args) throws FileNotFoundException {//同步读取文件内容FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls"));List<UserReadEntity> list = EasyExcel.read(inputStream).head(UserReadEntity.class).sheet().doReadSync();System.out.println(JSONArray.toJSONString(list));
}

运行程序,输出结果如下:

[{"age":20,"name":"张三0","time":1616920360000},{"age":21,"name":"张三1","time":1616920360000},{"age":22,"name":"张三2","time":1616920360000},{"age":23,"name":"张三3","time":1616920360000},{"age":24,"name":"张三4","time":1616920360000},{"age":25,"name":"张三5","time":1616920360000},{"age":26,"name":"张三6","time":1616920360000},{"age":27,"name":"张三7","time":1616920360000},{"age":28,"name":"张三8","time":1616920360000},{"age":29,"name":"张三9","time":1616920360000}]
2.3、动态自由导出导入

在实际使用开发中,我们不可能每来一个 excel 导入导出需求,就编写一个实体类,很多业务需求需要根据不同的字段来动态导入导出,没办法基于实体类注解的方式来读取文件或者写入文件。

因此,基于EasyExcel提供的动态参数化生成文件和动态监听器读取文件方法,我们可以单独封装一套动态导出导出工具类,省的我们每次都需要重新编写大量重复工作,以下就是小编我在实际使用过程,封装出来的工具类,在此分享给大家!

  • 首先,我们可以编写一个动态导出工具类

public class DynamicEasyExcelExportUtils {private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtils.class);private static final String DEFAULT_SHEET_NAME = "sheet1";/*** 动态生成导出模版(单表头)* @param headColumns 列名称* @return            excel文件流*/public static byte[] exportTemplateExcelFile(List<String> headColumns){List<List<String>> excelHead = Lists.newArrayList();headColumns.forEach(columnName -> { excelHead.add(Lists.newArrayList(columnName)); });byte[] stream = createExcelFile(excelHead, new ArrayList<>());return stream;}/*** 动态生成模版(复杂表头)* @param excelHead   列名称* @return*/public static byte[] exportTemplateExcelFileCustomHead(List<List<String>> excelHead){byte[] stream = createExcelFile(excelHead, new ArrayList<>());return stream;}/*** 动态导出文件(通过map方式计算)* @param headColumnMap  有序列头部* @param dataList       数据体* @return*/public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){//获取列名称List<List<String>> excelHead = new ArrayList<>();if(MapUtils.isNotEmpty(headColumnMap)){//key为匹配符,value为列名,如果多级列名用逗号隔开headColumnMap.entrySet().forEach(entry -> {excelHead.add(Lists.newArrayList(entry.getValue().split(",")));});}List<List<Object>> excelRows = new ArrayList<>();if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){for (Map<String, Object> dataMap : dataList) {List<Object> rows = new ArrayList<>();headColumnMap.entrySet().forEach(headColumnEntry -> {if(dataMap.containsKey(headColumnEntry.getKey())){Object data = dataMap.get(headColumnEntry.getKey());rows.add(data);}});excelRows.add(rows);}}byte[] stream = createExcelFile(excelHead, excelRows);return stream;}/*** 生成文件(自定义头部排列)* @param rowHeads* @param excelRows* @return*/public static byte[] customerExportExcelFile(List<List<String>> rowHeads, List<List<Object>> excelRows){//将行头部转成easyexcel能识别的部分List<List<String>> excelHead = transferHead(rowHeads);return createExcelFile(excelHead, excelRows);}/*** 生成文件* @param excelHead* @param excelRows* @return*/private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){try {if(CollectionUtils.isNotEmpty(excelHead)){ByteArrayOutputStream outputStream = new ByteArrayOutputStream();EasyExcel.write(outputStream).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).head(excelHead).sheet(DEFAULT_SHEET_NAME).doWrite(excelRows);return outputStream.toByteArray();}} catch (Exception e) {log.error("动态生成excel文件失败,headColumns:" + JSONArray.toJSONString(excelHead) + ",excelRows:" + JSONArray.toJSONString(excelRows), e);}return null;}/*** 将行头部转成easyexcel能识别的部分* @param rowHeads* @return*/public static List<List<String>> transferHead(List<List<String>> rowHeads){//将头部列进行反转List<List<String>> realHead = new ArrayList<>();if(CollectionUtils.isNotEmpty(rowHeads)){Map<Integer, List<String>> cellMap = new LinkedHashMap<>();//遍历行for (List<String> cells : rowHeads) {//遍历列for (int i = 0; i < cells.size(); i++) {if(cellMap.containsKey(i)){cellMap.get(i).add(cells.get(i));} else {cellMap.put(i, Lists.newArrayList(cells.get(i)));}}}//将列一行一行加入realHeadcellMap.entrySet().forEach(item -> realHead.add(item.getValue()));}return realHead;}/*** 导出文件测试* @param args* @throws IOException*/public static void main(String[] args) throws IOException {//导出包含数据内容的文件(方式一)LinkedHashMap<String, String> headColumnMap = Maps.newLinkedHashMap();headColumnMap.put("className","班级");headColumnMap.put("name","学生信息,姓名");headColumnMap.put("sex","学生信息,性别");List<Map<String, Object>> dataList = new ArrayList<>();for (int i = 0; i < 5; i++) {Map<String, Object> dataMap = Maps.newHashMap();dataMap.put("className", "一年级");dataMap.put("name", "张三" + i);dataMap.put("sex", "男");dataList.add(dataMap);}byte[] stream1 = exportExcelFile(headColumnMap, dataList);FileOutputStream outputStream1 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx"));outputStream1.write(stream1);outputStream1.close();//导出包含数据内容的文件(方式二)//头部,第一层List<String> head1 = new ArrayList<>();head1.add("第一行头部列1");head1.add("第一行头部列1");head1.add("第一行头部列1");head1.add("第一行头部列1");//头部,第二层List<String> head2 = new ArrayList<>();head2.add("第二行头部列1");head2.add("第二行头部列1");head2.add("第二行头部列2");head2.add("第二行头部列2");//头部,第三层List<String> head3 = new ArrayList<>();head3.add("第三行头部列1");head3.add("第三行头部列2");head3.add("第三行头部列3");head3.add("第三行头部列4");//封装头部List<List<String>> allHead = new ArrayList<>();allHead.add(head1);allHead.add(head2);allHead.add(head3);//封装数据体//第一行数据List<Object> data1 = Lists.newArrayList(1,1,1,1);//第二行数据List<Object> data2 = Lists.newArrayList(2,2,2,2);List<List<Object>> allData = Lists.newArrayList(data1, data2);byte[] stream2 = customerExportExcelFile(allHead, allData);FileOutputStream outputStream2 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user6.xlsx"));outputStream2.write(stream2);outputStream2.close();}
}
  • 然后,编写一个动态导入工具类

/*** 创建一个文件读取监听器*/
public class DynamicEasyExcelListener extends AnalysisEventListener<Map<Integer, String>> {private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class);/*** 表头数据(存储所有的表头数据)*/private List<Map<Integer, String>> headList = new ArrayList<>();/*** 数据体*/private List<Map<Integer, String>> dataList = new ArrayList<>();/*** 这里会一行行的返回头** @param headMap* @param context*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));//存储全部表头数据headList.add(headMap);}/*** 这个每一条数据解析都会来调用** @param data*            one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));dataList.add(data);}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库LOGGER.info("所有数据解析完成!");}public List<Map<Integer, String>> getHeadList() {return headList;}public List<Map<Integer, String>> getDataList() {return dataList;}
}
  • 动态导入工具类

/*** 编写导入工具类*/
public class DynamicEasyExcelImportUtils {/*** 动态获取全部列和数据体,默认从第一行开始解析数据* @param stream* @return*/public static List<Map<String,String>> parseExcelToView(byte[] stream) {return parseExcelToView(stream, 1);}/*** 动态获取全部列和数据体* @param stream           excel文件流* @param parseRowNumber   指定读取行* @return*/public static List<Map<String,String>> parseExcelToView(byte[] stream, Integer parseRowNumber) {DynamicEasyExcelListener readListener = new DynamicEasyExcelListener();EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead();List<Map<Integer, String>> headList = readListener.getHeadList();if(CollectionUtils.isEmpty(headList)){throw new RuntimeException("Excel未包含表头");}List<Map<Integer, String>> dataList = readListener.getDataList();if(CollectionUtils.isEmpty(dataList)){throw new RuntimeException("Excel未包含数据");}//获取头部,取最后一次解析的列头数据Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() -1);//封装数据体List<Map<String,String>> excelDataList = Lists.newArrayList();for (Map<Integer, String> dataRow : dataList) {Map<String,String> rowData = new LinkedHashMap<>();excelHeadIdxNameMap.entrySet().forEach(columnHead -> {rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey()));});excelDataList.add(rowData);}return excelDataList;}/*** 文件导入测试* @param args* @throws IOException*/public static void main(String[] args) throws IOException {FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx"));byte[] stream = IoUtils.toByteArray(inputStream);List<Map<String,String>> dataList = parseExcelToView(stream, 2);System.out.println(JSONArray.toJSONString(dataList));inputStream.close();}
}

为了方便后续的操作流程,在解析数据的时候,会将列名作为key

03、小结

在实际的业务开发过程中,根据参数动态实现 Excel 的导出导入还是非常广的。

当然,EasyExcel 的功能还不只上面介绍的那些内容,还有基于模版进行 excel的填充,web 端 restful 的导出导出,使用方法大致都差不多!

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

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

相关文章

k8s综合项目

一、准备环境 1.1 部署服务器 在centos7.9系统里搭建v1.23版本的k8s集群&#xff0c;准备四台服务器&#xff0c;两台作为master&#xff0c;主机名分别为 k8s-master和k8s-master-2&#xff0c;主机名为k8s-master&#xff0c;两台作为 node&#xff0c;主机名分别为k8s-nod…

11-sentinel利用nacos作持久化

本文介绍sentinel配置数据的持久化方法。由于sentinel官方并没有提供持久化功能&#xff0c;大家在测试过程中也能发现sentinel服务重启后&#xff0c;原来配置的数据就丢了&#xff0c;本文就是来处理这一问题的。 做好心理准备&#xff0c;我们要修改sentinel的源代码&#…

C++ | Leetcode C++题解之第350题两个数组的交集II

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {sort(nums1.begin(), nums1.end());sort(nums2.begin(), nums2.end());int length1 nums1.size(), length2 nums2…

【C++ 第十五章】map 和 set 的封装(封装红黑树)

1. map 和 set 的介绍 ⭐map 与 set 分别是STL中的两种序列式容器; 它们是一种树形数据结构的容器&#xff0c;且其的底层构造为一棵红黑树; 而在上一篇文章中提到,其实红黑树本身就是一棵二叉搜索树,是基于二叉搜索树的性质对其增加了平衡的属性来提高其综合性能 ⭐当然也…

【论文阅读】Retargeting and Respecializing GPU Workloads for Performance Portability

摘要 为了接近峰值性能&#xff0c;像gpu这样的加速设备需要大量的特定于架构的调优&#xff0c;以了解共享内存、并行性、tensor core等的可用性。不幸的是&#xff0c;对更高性能和更低成本的追求导致了架构设计的显著多样化&#xff0c;甚至是产自同一供应商的产品也是如此。…

Apache CloudStack Official Document 翻译节选(七)

关于 Apache CloudStack 的 最佳实践 &#xff08;一&#xff09; Best Practices 部署Apache CloudStack是极具挑战性的&#xff0c;在整个部署过程中需要你做出形形色色的技术性选择。Apache CloudStack的配置条目是相当灵活的&#xff0c;这是因为在组合和配置具体条目时有…

【深入浅出Docker】【三】Docker容器详解

文章目录 一. Docker容器简介二. Docker容器详解1. 容器vs虚拟机1.1. 虚拟机模型1.2. 容器模型1.3. 虚拟机的额外开销 2. 容器启动过程描述3. 容器进程4. 容器生命周期与文件保存5. 优雅地停止容器&#xff1a;两阶段方式停止并删除容器6. 利用重启策略进行容器的自我修复6.1. …

SpringBoot依赖之Spring Data Redis实现位图Bitmap

Spring Boot 项目中使用 Spring Data Redis 实现位图Bitmap 暂未发表&#xff0c;记录于20240820 概念 Spring Data Redis (AccessDriver) 依赖名称: Spring Data Redis (AccessDriver)功能描述: Advanced and thread-safe Java Redis client for synchronous, asynchronous,…

学习 node.js 六 Markdown 转为 html,zlib

目录 Markdown 转为 html 安装 ejs语法 标签含义 1. 纯文本标签 2. 输出经过 HTML 转义的内容 3. 输出非转义的内容(原始内容) marked browserSync zlib gzip deflate gzip 和 deflate 区别 http请求压缩 Markdown 转为 html 什么是markdown&#xff1f; Markdo…

分享思源笔记的几个骚操作

文章目录 思维导图复习法效果视频制作过程使用方法 大纲复习方法制作过程 人工智能简易使用效果制作过程 思维导图复习法 效果视频 bandicam20240817222246034.mp4 制作过程 首先下载【写味】主题或者是[自定义块样式]插件 他两个的区别是 思维导图以列表形式写出来 选择转…

【2025校招】4399 NLP算法工程师笔试题

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/19 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷分为单选&#xff0c;自我评价题&#xff0c;编程题 单选和自我评价这里不再介绍&#xff0c;4399的编程题一如既往地抽象&#xff…

redis AOF机制

在redis运行期间&#xff0c;不断将redis执行的写命令写到文件中&#xff0c;redis重启之后&#xff0c;只要将这些命令重复执行一遍就可以恢复数据。因为AOF只是将少量的写命令写入AOF文件中&#xff0c;因此其执行效率高于RDB&#xff0c;开启AOF即使Redis发生故障&#xff0…

前端使用miniO上传文件

项目背景:vue2&#xff0c;前提是请先安装miniO,若安装引入时报错&#xff0c;那就是版本不对&#xff0c;通常指定版本安装即可。 页面样式&#xff1a; 前端vue页面代码&#xff1a; //<el-form>表单中:<el-form-item label"文件" prop"fileIds&q…

TY6802 同步整流PCB设计注意事项

TY6802 系列是一款用于反激式电源次级同步整流芯片&#xff0c;TY6802能可靠支持包括 DCM、CCM和准谐振模式。TY6802 集成了一个 100V 功率 MOSFET&#xff08;TY6802A&#xff1a;100V15mR; TY6802B&#xff1a;100V10mR; TY6802C&#xff1a;100V7.5mR;) &#xff0c;可以取代…

API容易被攻击,如何做好API安全

随着互联网技术的飞速发展和普及&#xff0c;网络安全问题日益严峻&#xff0c;API&#xff08;应用程序接口&#xff09;已成为网络攻击的常见载体之一。API作为不同系统之间数据传输的桥梁&#xff0c;其安全性直接影响到整个系统的稳定性和数据的安全性。 根据Imperva发布的…

JavaScript(25)——BOM、延迟函数、JS执行机制

BOM BOM是浏览器对象模型 window对象是一个全局对象&#xff0c;也就是JavaScript中的顶级对象所有通过var定义的全局作用域中的变量&#xff0c;函数都会变成window对象的属性和方法window对象下的属性和方法调用的时候可以省略window 延时函数 let a setTimeout(回调函数…

OLED整体刷新到结合switch刷新方式演变

OLED整体刷新到结合switch刷新方式演变 引言 OLED刷新模式, 其实很简单, 就和prinf输出一样, 只是我们这里利用OLED来输出我们所需要的东西了。 至于OLED单独整体刷新, 还是利用switch刷新, 都是形而上学, 形的东西, 至于底层, 江协科技大佬已经帮我整理好了, 我们是站在巨人的…

【Python零基础学习】字典

文章目录 前言一、简单字典示例二、使用字典三、字典遍历四、嵌套总结 前言 Python 字典 是一种非常强大且灵活的数据结构&#xff0c;它允许你通过键&#xff08;key&#xff09;来存储和检索值&#xff08;value&#xff09;。想象一下&#xff0c;字典就像一个巨大的电话簿…

8月21日微语报,星期三,农历七月

8月21日微语报&#xff0c;星期三&#xff0c;农历七月十八&#xff0c;工作愉快&#xff0c;生活喜乐&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、今日出发&#xff01;中国体育代表团将分两批出征巴黎残奥会。 2、股价再创新高&#xff01;工商银行市值…

suricata编译安装和运行

目录 编译安装 运行 调试 编译安装 apt -y install autoconf automake build-essential cargo \ libjansson-dev libpcap-dev libpcre2-dev libtool \ libyaml-dev make pkg-config rustc zlib1g-dev apt-get install libpcre3-dev wget https://www.openin…