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,事件监听器为listener
        EasyExcel.read(FILEPATH, addCodeByExcelVo.class, listener).extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
        // 5. 将事件监听器中的addCodeByExcelVo列表赋值给addCodeByExcelVoList
        addCodeByExcelVoList = 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等于指定位置,则将该字段的值设置为filedValue
            if (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<>();
 
    @Override
    public void invoke(addCodeByExcelVo excelData, AnalysisContext analysisContext) {
        log.info(" data -> {}", excelData);
        list.add(excelData);
    }
 
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        CellExtraTypeEnum type = extra.getType();
        switch (type) {
            case MERGE: {
                if (extra.getRowIndex() >= headRowNum) {
                    cellExtraList.add(extra);
                }
                break;
            }
            default:{
            }
        }
    }
 
    @Override
    public 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~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于VS code 实现Java前后端打通—基础—使用Springboot+postgreSql+mybatis+Navicat

前言&#xff1a; 作者学习webjava后的而总结&#xff0c;总的流程概括就是先使用springboot创建项目&#xff0c;在application.properties中完成相应的postgreSql和mybaits的环境配置和.xml文件中dependecy依赖配置&#xff0c;entities实现数据表的类型模板&#xff0c;分别…

【机器学习】包裹式特征选择之序列前向选择法

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

day6:STM32MP157——串口通信实验

使用的是cortex A7内核 【串口通信的工作原理】 本次实验使用的是uart4的串口&#xff0c;分别使用了uart4_tx和uart4_rx两个引脚。根据板子的原理图我们可以知道&#xff0c;他们分别对应着芯片的PG11和PB2 从引脚名字也可以知道使用了GPIO口&#xff0c;所以本次实验同样需…

MCGS学习——用户管理

用户管理介绍 用户管理主要是为了实现触摸屏的安全操作&#xff0c;工业过程控制中&#xff0c;应该尽量避免由于人为的误操作所引发的故障或事故&#xff0c;而某些失误带来的后果是致命的&#xff1b;通过用户管理严格限制各类操作的权限&#xff0c;使不具备操作资格的人员…

软考高级:架构与中间件技术-软件复用概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

CHAT~(持续更新)

CHAT&#xff08;持续更新&#xff09; 实现一个ChatGPT创建API设计页面布局业务操作技术架构 编码其他 实现一个ChatGPT 创建API 最简单也最需要信息的一步 继续往下做的前提 此处省略&#xff0c;想要获取接口创建方式联系 设计 页面布局 按照官网布局 业务操作 注册登…

Linux 进程通信:匿名管道、实现进程池

目录 一、进程间通信 1、 为什么需要进程通信 2、发展和分类 二、管道 1、概念 2、特点 2、复制并共享 3、用fork来共享管道原理 4、站在文件描述符角度-深度理解管道 5、站在内核角度-管道本质 三、匿名管道 1、概念 2、创建 3、snprintf 4、父子进程中进行单…

抽取CLOB字段中XML的特定元素的VALUE值

在ORACLE数据库中&#xff0c;有时XML文件会被保存在CLOB字段中。 这时候&#xff0c;若是我们要获取此字段XML中特定元素的VALUE值&#xff0c;就需要用到xmltype 这个函数。 如下面的 XML文件&#xff0c;保存在 TABLE_A 的CLOB_K 字段&#xff0c;若是我们要获取其中的 Y…

onnx | onnx-simplifier安装和使用

安装&#xff1a; pip install -i https://pypi.douban.com/simple onnx-simplifierpip install -i http://mirrors.aliyun.com/pypi/simple onnx-simplifier 使用&#xff1a; python -m onnxsim face.onnx face_sim.onnx

Unity Canvas的三种模式

一、简介&#xff1a; Canvas的Render Mode一共有三种模式&#xff1a;Screen Space -OverLay、Screen Space-Camera、World Space Screen Space - Overlay&#xff08;屏幕空间 - 覆盖&#xff09;&#xff1a; 这是最简单的 Canvas 渲染模式。UI 元素在这个模式下将渲染在屏…

Oracle参数文件详解

1、参数文件的作用 参数文件用于存放实例所需要的初始化参数&#xff0c;因为多数初始化参数都具有默认值&#xff0c;所以参数文件实际存放了非默认的初始化参数。 2、参数文件类型 1&#xff09;服务端参数文件&#xff0c;又称为 spfile 二进制的文件&#xff0c;命名规则…

PostgreSQL11 | Windows系统安装PostgreSQL

本教程选取与参考书籍《PostgreSql11 从入门到精通》&#xff08;清华大学出版社&#xff09;的11大版本最新小版本11.22的安装作为教程案例 下载 下载PostgreSQL installer 下载到本地 安装 运行安装引导器 中国地区语言选项&#xff08;暂时&#xff09; Chinese(Simplifie…

2024牛客寒假算法基础集训营4补题

E&#xff1a;贪心数据结构 首先&#xff0c;我们看一个例子&#xff1a; 114514&#xff0c;令k3,我们从左开始&#xff0c;1&#xff0c;1&#xff0c;4&#xff0c;此时为3的倍数&#xff0c;那么我们就截断。 因为若我们在此截断&#xff0c;后面的5会对以后的数产生有利…

SSM | SSM框架整合

目录: 一、整合环境搭建整合思路准备所需JAR包编写配置文件 二、整合应用测试 作者简介 &#xff1a;一只大皮卡丘&#xff0c;计算机专业学生&#xff0c;正在努力学习、努力敲代码中! 让我们一起继续努力学习&#xff01; 该文章参考学习教材为&#xff1a; 《Java EE企业级应…

Qt——2D画图

基础画图函数 矩形 painter.drawRect(50,50,200,100); 圆角矩形 painter.drawRoundRect(50,50,200,200,50,50); xRadius和yRadius分别以矩形宽度和高度的一半的百分比指定&#xff0c;并且应该在0.0到100.0的范围内 弧线 painter.drawArc(50,50,200,200, -90*16, 90*16);…

基于nodejs+vue学生作业管理系统python-flask-django-php

他们不仅希望页面简单大方&#xff0c;还希望操作方便&#xff0c;可以快速锁定他们需要的线上管理方式。基于这种情况&#xff0c;我们需要这样一个界面简单大方、功能齐全的系统来解决用户问题&#xff0c;满足用户需求。 课题主要分为三大模块&#xff1a;即管理员模块和学生…

[AutoSar]BSW_ECUC模块配置

目录 关键词平台说明一、背景二、EcucGeneral2.1 BswInitialization 三、EcucHardware四、EcucPduCollection五、EcucPartitionCollection 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &#xff0c; EB芯片厂商TI 英飞凌编程语…

三星解释其 108MP Nonacell 传感器中的 PDAF 像素遮蔽

Electronic Imaging 发表了三星论文“采用 Nonacell 和 Super PD 的 CMOS 图像传感器的新型 PDAF 校正方法,以提高合并模式下的图像质量”,作者为 Yeongheup Jang、Hyungwook Kim、Kundong Kim、Sungsu Kim、Sungyong Lee 和 Joonseo Yim。 本文提出了一种新的 PDAF 校正方法…

【stable diffusion扩散模型】一篇文章讲透

目录 一、引言 二、Stable Diffusion的基本原理 1 扩散模型 2 Stable Diffusion模型架构 3 训练过程与算法细节 三、Stable Diffusion的应用领域 1 图像生成与艺术创作 2 图像补全与修复 3 其他领域 四、Stable Diffusion的优势与挑战 &#x1f449;优势 &#x1f…