Springboot实现合并单元格的excel文件导入到数据库(多模块)

最近做项目的时候一直在遇到excel导入导出的问题,本篇博文也是为了记录我这几天的血泪史,并做以记录,希望各位看完之后能有所收获。

以下是我excel文档里面的具体内容:

excel文件中的编码信息属于另外一张表,所以以下的代码是两张表的同时新增,读者看完后可以根据自己的代码逻辑进行修改。

实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
public class addCodeManageVo implements Serializable {private String codeName;private String codeDetail;private List<CodeValueManage> codeValueManages;}
package com.datapojo.bean;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;import java.io.Serializable;
import java.util.Date;/*** <p>* * </p>** @author yinan* @since 2024-03-08*/
@Getter
@Setter
@Builder
@TableName("code_value_manage")
@ApiModel(value = "CodeValueManage对象")
@AllArgsConstructor
@NoArgsConstructor
public class CodeValueManage implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty("码值id")@TableId(value = "code_id", type = IdType.AUTO)private Integer codeId;@ApiModelProperty("码值名称")@TableField("code_name")private String codeName;@ApiModelProperty("码值取值")@TableField("code_value")private String codeValue;@ApiModelProperty("码值含义")@TableField("code_description")private String codeDescription;@ApiModelProperty("码表编号")@TableField("code_table_number")private Integer codeTableNumber;@ApiModelProperty("状态(0:已发布  1:未发布 2:已停用)")@TableField("status")private Integer status;@ApiModelProperty("创建时间")@TableField("create_time")@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")private Date createTime;@ApiModelProperty("修改时间")@TableField("update_time")@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")private Date updateTime;
//
//    @ApiModelProperty("码值编号")
//    @TableField("code_value_number")
//    private String codeValueNumber;public CodeValueManage(String codeName, String codeValue, String codeDetail) {this.codeName=codeName;this.codeValue=codeValue;this.codeDescription=codeDetail;}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class addCodeByExcelVo implements Serializable {@ExcelProperty(value="码表名称",index=0)private String codeName;@ExcelProperty(value="码表说明",index = 1)private String codeDetail;@ExcelProperty(value={"编码信息","编码取值"},index = 2)private String codeValue;@ExcelProperty(value={"编码信息","编码名称"},index=3)private String codeValueName;@ExcelProperty(value={"编码信息","编码含义"},index=4)private String codeDescription;
}
Excel工具类:
package com.datauser.config;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.datapojo.vo.addCodeByExcelVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;@Slf4j
@Configuration
public class ExcelConfig {//我们的表格一般都是有头的,头的内容可能是2行,可能是1行,
// 在进行合并单元格处理的时候,需要考虑这个行数。我们定义了一个常量HEAD_ROW_NUM来记录这个行数,最后进行单元格值计算的时候传入。private static final int HEAD_ROW_NUM = 2;/*** 将文件保存在本地* @param file* @return* @throws IOException*/private String uploadFile(MultipartFile file) throws IOException {
//        获取文件名以及文件类型,并使用uuid生成一个随机的新的文件名String filename = file.getOriginalFilename();String filetype = filename.substring(filename.lastIndexOf("."));String newfilename = filename.substring(0, filename.lastIndexOf(".")) + "_Temp" + filetype;
//        创建一个文件File file1 = new File("E:/PictureTool/UploadFile/");if (!file1.exists()) {file1.mkdirs();}
//        将文件上传到指定目录try {file.transferTo(new File("E:/PictureTool/UploadFile/" + newfilename));System.out.println("文件上传成功");return "E:/PictureTool/UploadFile/" + newfilename;} catch (Exception e) {e.printStackTrace();System.out.println("文件上传失败");return "文件上传失败";}}// list方法:接收一个MultipartFile文件,返回一个List<addCodeByExcelVo>对象public List<addCodeByExcelVo> list(MultipartFile file) throws IOException {// 1. 将文件上传到临时目录String FILEPATH = uploadFile(file);// 2. 创建一个空的addCodeByExcelVo列表List<addCodeByExcelVo> addCodeByExcelVoList;// 3. 创建一个自定义的事件监听器,用于处理读取Excel文件的过程CustomAnalysisEventListener listener = new CustomAnalysisEventListener(HEAD_ROW_NUM);// 4. 使用EasyExcel读取Excel文件,指定要读取的类为addCodeByExcelVo,事件监听器为listenerEasyExcel.read(FILEPATH, addCodeByExcelVo.class, listener).extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();// 5. 将事件监听器中的addCodeByExcelVo列表赋值给addCodeByExcelVoListaddCodeByExcelVoList = listener.getList();// 6. 获取事件监听器中的cellExtra列表List<CellExtra> cellExtraList = listener.getCellExtraList();// 7. 如果cellExtra列表不为空,则调用mergeaddCodeByExcelVo方法,合并addCodeByExcelVo列表if (cellExtraList != null && cellExtraList.size() > 0) {mergeaddCodeByExcelVo(addCodeByExcelVoList, cellExtraList, HEAD_ROW_NUM);}// 8. 返回addCodeByExcelVo列表return addCodeByExcelVoList;}// mergeaddCodeByExcelVo方法:用于合并addCodeByExcelVo列表private void mergeaddCodeByExcelVo(List<addCodeByExcelVo> addCodeByExcelVoList, List<CellExtra> cellExtraList, int headRowNum) {// 遍历cellExtra列表cellExtraList.forEach(cellExtra -> {// 获取第一个单元格和最后一个单元格的行索引和列索引int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNum;int lastRowIndex = cellExtra.getLastRowIndex() - headRowNum;int firstColumnIndex = cellExtra.getFirstColumnIndex();int lastColumnIndex = cellExtra.getLastColumnIndex();// 获取初始值Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, addCodeByExcelVoList);// 遍历行for (int i = firstRowIndex; i <= lastRowIndex; i++) {// 遍历列for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {// 将初始值设置到addCodeByExcelVo列表的对应位置setInitValueToList(initValue, i, j, addCodeByExcelVoList);}}});}// setInitValueToList方法:用于将一个值设置到addCodeByExcelVo列表的指定位置private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<addCodeByExcelVo> data) {// 获取addCodeByExcelVo列表中的指定行对象addCodeByExcelVo object = data.get(rowIndex);// 遍历对象中的字段for (Field field : object.getClass().getDeclaredFields()) {field.setAccessible(true);ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);// 如果字段有ExcelProperty注解,且注解的index等于指定位置,则将该字段的值设置为filedValueif (annotation != null && annotation.index() == columnIndex) {try {field.set(object, filedValue);break;} catch (IllegalAccessException e) {log.error("设置合并单元格的值异常:{}", e.getMessage());}}}}// getInitValueFromList方法:从addCodeByExcelVo列表中获取指定位置的值private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<addCodeByExcelVo> data) {Object filedValue = null;addCodeByExcelVo object = data.get(firstRowIndex);// 遍历对象中的字段for (Field field : object.getClass().getDeclaredFields()) {field.setAccessible(true);ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);// 如果字段有ExcelProperty注解,且注解的index等于指定位置,则返回该字段的值if (annotation != null && annotation.index() == firstColumnIndex) {try {filedValue = field.get(object);break;} catch (IllegalAccessException e) {log.error("设置合并单元格的初始值异常:{}", e.getMessage());}}}return filedValue;}}

package com.datauser.config;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.datapojo.vo.addCodeByExcelVo;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;@Slf4j
public class CustomAnalysisEventListener extends AnalysisEventListener<addCodeByExcelVo> {private int headRowNum;public CustomAnalysisEventListener(int headRowNum) {this.headRowNum = headRowNum;}private List<addCodeByExcelVo> list = new ArrayList<>();private List<CellExtra> cellExtraList = new ArrayList<>();@Overridepublic void invoke(addCodeByExcelVo excelData, AnalysisContext analysisContext) {log.info(" data -> {}", excelData);list.add(excelData);}@Overridepublic void extra(CellExtra extra, AnalysisContext context) {CellExtraTypeEnum type = extra.getType();switch (type) {case MERGE: {if (extra.getRowIndex() >= headRowNum) {cellExtraList.add(extra);}break;}default:{}}}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}public List<addCodeByExcelVo> getList() {return list;}public List<CellExtra> getCellExtraList() {return cellExtraList;}
}

以上代码直接复制到你项目的对应包下面即可,代码中均做有注释,有疑问的请在评论区留言~

如果没有什么问题,最后list方法返回的结果应该是以下列表:

接下来是service实现类里面的具体实现方法:
  @Transactional(rollbackFor = Exception.class)public R importcodeinfo(MultipartFile file) throws IOException {List<addCodeByExcelVo> re = excelConfig.list(file);int reSize = re.size();System.out.println("reSize:" + reSize);for (addCodeByExcelVo ev : re) {System.out.println(ev.toString());}List<CodeValueManage> codeValueManage = new ArrayList<>();List<addCodeManageVo> codeManageVos = null;CodeValueManage cv = null;String codename = re.get(0).getCodeName(), codedetail = re.get(0).getCodeDetail();
//       获取最后一个码表名称的开始索引int listLength = re.size(), endIndex = listLength, target = 0;for (int i = listLength - 1; i >= 0; i--) {if (!Objects.equals(re.get(i).getCodeName(), re.get(listLength - 1).getCodeName())) {break;}endIndex--;}System.out.println("endIndex:" + endIndex);try{for (addCodeByExcelVo ev : re) {cv = new CodeValueManage();
//           判断当前行的码表是否与上一行相同if (!codename.equals(ev.getCodeName())&&!codedetail.equals(ev.getCodeDetail())) {codeManageVos = new ArrayList<>();codeManageVos.add(new addCodeManageVo(codename, codedetail, codeValueManage));codename = ev.getCodeName();codedetail = ev.getCodeDetail();
//          codeValueManage需要每次new一次,而不是直接清空,清空的话会直接把前面已经存进去的值也清空,导致前面的值丢失
//                    再进行下一次添加值的时候会把当前值也添加到前面的值当中
//                    为什么每次都要new呢?是因为这样做就会开辟一个新的空间,不会把当前的值添加到前面的值当中(更新了之前添加的元素(覆盖了之前添加的元素),所以和期望出现的不一致。)codeValueManage=new ArrayList<>();
//               清空当前码表的值
//                codeValueManage.clear();}if (target >= endIndex) {target++;cv.setCodeValue(ev.getCodeValue());cv.setCodeName(ev.getCodeValueName());cv.setCodeDescription(ev.getCodeDescription());codeValueManage.add(cv);} else {target++;cv.setCodeValue(ev.getCodeValue());cv.setCodeName(ev.getCodeValueName());cv.setCodeDescription(ev.getCodeDescription());codeValueManage.add(cv);}}System.out.println("target:"+target);
//       codeValueManage=new ArrayList<>();codeManageVos.add(new addCodeManageVo(codename, codedetail, codeValueManage));
//       遍历excel表,调用新增码表方法System.out.println("codeManageVos:" + codeManageVos.size());for (addCodeManageVo acmv :codeManageVos) {addcodeinfo(acmv);}return R.Success("文件导入成功");}catch (Exception e){e.printStackTrace();return R.Failed("文件导入失败");}}
注意:

1、需要注意的是,codeValueManage需要每次new一次,而不是直接清空,清空的话会直接把前面已经存进去的值也清空,导致前面的值丢失 ,再进行下一次添加值的时候会把当前值也添加到前面的值当中 。

2、为什么每次都要new呢?是因为这样做就会开辟一个新的空间,不会把当前的值添加到前面的值当中(更新了之前添加的元素(覆盖了之前添加的元素),所以和期望出现的不一致。)

当然,我的建议是读者可以根据自己代码的具体逻辑来处理列表导入到自己数据库的实现方法。

最后的addcodeinfo(acmv)方法是我自己写的一个方法,读者可以根据自己的实际需求来写这个新增到数据库里面的方法,这里我就不贴出来的,如果有需要请在后台私信我~

需要注意的是这里面的依赖我没有进行记录,因为我当时写的时候忘记了自己导入了哪些依赖(具体可以根据我代码中的import里面的来进行导入),辛苦各位得自己去查阅一下了(手动抱拳~)

以上就是实现excel导入数据库的所有方法了,希望各位能一帆风顺,没有bug~

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

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

相关文章

android emulator windows bat启动

android emulator windows bat启动 先上结果 // 模拟器路径 -netspeed full -avd 模拟器名称 C:\Users\name\AppData\Local\Android\Sdk\emulator\emulator.exe -netdelay none -netspeed full -avd Pixel_3a_API_34_extension_level_7_x86_64一般来说 windows 如果不做…

2023年全国职业院校技能大赛(网络系统管理赛项)样题一

2023****年全国职业院校技能大赛 GZ073****网络系统管理赛项 赛题第1套 模块A&#xff1a;网络构建 目 录 任务清单… 1 &#xff08;一&#xff09;基础配置… 1 &#xff08;二&#xff09;有线网络配置… 1 &#xff08;三&#xff09;无线网络配置… 3 &#xff0…

初探Flink集群【持续更新】

周末下雨&#xff0c;倒杯茶&#xff0c;在家练习Flink相关。 开发工具&#xff1a;IntelliJ Idea 第一步、创建项目 打开Idea&#xff0c;新建Maven项目&#xff0c;包和项目命名 在pom.xml 文件中添加依赖 <properties><flink.version>1.13.0</flink.vers…

使用Python进行股票分析(2)

简介 我们在之前的文章《使用Python进行股票分析&#xff08;1&#xff09;》中&#xff0c;通过自动获取股票的历史数据&#xff0c;然后选择在一定时间内处于上涨的股票作为我们投资的标的。在本文中&#xff0c;我们进一步通过分析股票的短期趋势&#xff0c;选择处于短期上…

Ubuntu Desktop 安装谷歌拼音输入法

Ubuntu Desktop 安装谷歌拼音输入法 1. Installation1.1. 汉语语言包​1.2. 谷歌拼音输入法1.3. 安装语言包1.4. 键盘输入方式系统1.5. 重启电脑1.6. 输入法配置 2. configuration2.1. Text Entry Settings… 3. ExecutionReferences 1. Installation 1.1. 汉语语言包 strong…

springcloud第4季 负载均衡的介绍3

一 loadbalance 1.1 负载均衡的介绍 使用注解loadbalance&#xff0c;是一个客户端的负载均衡器&#xff1b;通过之前已经从注册中心拉取缓存到本地的服务列表中&#xff0c;获取服务进行轮询负载请求服务列表中的数据。 轮询原理 1.2 loadbalance工作流程 loadBalance工作…

再仔细品品Elasticsearch的向量检索

我在es一开始有向量检索&#xff0c;就开始关注这方面内容了。特别是在8.X之后的版本&#xff0c;更是如此。我也已经把它应用在亿级的生产环境中&#xff0c;用于多模态检索和语义检索&#xff0c;以及RAG相关。 也做过很多的优化&#xff1a;ES 8.x 向量检索性能测试 & 把…

Swift 从获取所有 NSObject 对象聊起:ObjC、汇编语言以及底层方法调用链(三)

概览 承接上一篇博文: Swift 从获取所有 NSObject 对象聊起:ObjC、汇编语言以及底层方法调用链(二)我们在其中讨论了如何使用第三方强大通用的钩子库 SwiftHook 来协助我们完成 NSObject 构造器 init 的 SWIZZ 操作。我们还讨论了为什么用 print 打印对象信息时会发生崩溃…

Unity 布局元素Layout Element

Layout Element是一种用于控制UI元素在布局组件&#xff08;如Horizontal Layout Group、Vertical Layout Group、Grid Layout Group、Content Size Fitter和Aspect Ratio Fitter&#xff09;中的大小和位置的组件。Layout Element组件可以附加到UI元素上&#xff0c;以便在布局…

opencv各个模块介绍(2)

Features2D 模块&#xff1a;特征检测和描述子计算模块&#xff0c;包括SIFT、SURF等算法。 Features2D 模块提供了许多用于特征检测和描述子匹配的函数和类&#xff0c;这些函数和类可用于图像特征的提取、匹配和跟踪。 FeatureDetector&#xff1a;特征检测器的基类&#xf…

arm 外部中断

main.c: #include"key_inc.h" //封装延时函数 void delay(int ms) {int i,j;for(i0;i<ms;i){for(j0;j<2000;j){}} } int main() {//按键中断的初始化key1_it_config();key2_it_config();key3_it_config();while(1){printf("in main pro\n");delay(1…

查看Linux系统重启的四种基本命令

目录 前言1. last2. uptime3. journalctl4. dmesg 前言 对于排查其原因推荐阅读&#xff1a;详细分析服务器自动重启原因&#xff08;涉及Linux、Window&#xff09; 在Linux中&#xff0c;有多种命令可以查看系统重启的信息 以下是其中一些常用的命令及其解释&#xff1a; …

EasyPOI操作Excel从零入门

教程介绍 我们不造轮子&#xff0c;只是轮子的搬运工。&#xff08;其实最好是造轮子&#xff0c;造比别人好的轮子&#xff09;开发中经常会遇到excel的处理&#xff0c;导入导出解析等等&#xff0c;java中比较流行的用poi&#xff0c;但是每次都要写大段工具类来搞定这事儿…

【新版】系统架构设计师 - 新版架构备考索引<附2023年11月原题回忆>

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 新版架构备考索引机考详情备考索引与方向&#xff08;个人观点&#xff0c;仅供参考&#xff09;总结附&#xff1a;2023年11月改版机试原题简单回忆 架构 - 新版架构备考索引 首先&#xff0c;此…

Spring 简介

1. Spring简介 1.1 Spring 核心设计思想 1.1.1 Spring 是什么&#xff1f; Spring 是包含了众多⼯具⽅法的 IoC 容器。Spring 指的是 Spring Framework&#xff08;Spring 框架&#xff09;&#xff0c;它是⼀个开源框架&#xff0c;Spring ⽀持⼴泛的应⽤场景&#xff0c;它…

月之暗面Kimi代码分析能力评测

最近打算重构一下PawSQL优化引擎中的OR条件的SELECT重写优化策略的代码&#xff0c;时间有点久&#xff0c;代码有点复杂&#xff0c;看到网上对新出了KIMI评价很高。于是尝试用它来理解一下代码。上传了此优化重写的代码&#xff0c;提问&#xff1a; 第一问&#xff0c;设计…

java多线程编程面试题总结

一些最基本的基础知识就不总结了&#xff0c;参考之前写的如下几篇博客&#xff0c;阅读顺序从上到下&#xff0c;依次递进。 java 多线程 多线程概述及其三种创建方式 线程的常用方法 java 线程安全问题 三种线程同步方案 线程通信&#xff08;了解&#xff09; java 线程池…

炸裂消息,全球首创全员持股短视频平台诞生

平台简介&#xff1a;享视是国内首家基于区块链打造的共益型短视频平台&#xff0c;也是全球首家真正践行共益模式的科技型创新企业&#xff0c;计划将80%的股权免费分发和共享给平台的会员&#xff0c;共同打造一家全员持股的共益商业模式示范标杆企业。 平台愿景&#xff1a…

Spark Streaming DStream

Spark Streaming DStream DStream 即Discretized Stream&#xff0c;中文叫做离散流&#xff0c;Spark Streaming提供的一种高级抽象&#xff0c;代表了一个持续不断的数据流。 DStream可以通过输入数据源来创建&#xff0c;比如Kafka、Flume&#xff0c;也可以通过对其他DS…

主干网络篇 | YOLOv8更换主干网络之MobileNetV3

前言:Hello大家好,我是小哥谈。MobileNetV3是一种轻量级的卷积神经网络架构,用于图像分类和目标检测任务。它是MobileNet系列的第三个版本,旨在在保持高准确性的同时减少模型的计算量和参数数量。MobileNetV3引入了一些新的设计思想和技术,以提高模型的性能。其中一项重要…