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

news2024/9/25 13:18:07

最近做项目的时候一直在遇到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~

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

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

相关文章

MySQL:表的操作

文章目录 创建表查看表结构修改表删除表 前面对于库的操作有了认识后&#xff0c;下面进行表的操作 创建表 以下图为例 创建表其实和定义结构体有点类似&#xff0c;总的来说就是先定义列名&#xff0c;然后后面跟着是列的数据类型&#xff0c;之后在定义结束后可以带上对应的…

SpringMVC | SpringMVC中的 “文件上传和下载”

目录: 一、文件上传1.1 文件上传“概述”1.2 文件上传“具体配置” :“前端”中配置“文件上传” ( type“file” 满足3个条件 )“后端”中配置“文件上传” ( 配置id为“CommonsMultipartResolver”的bean 配置“文件上传”的“约束条件” 通过“MultipartFile接口”参数接…

综合知识篇21-项目管理考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例…

02.percona Toolkit工具pt-archiver命令实践

1.命令作用 Percona Toolkit有的32个命令&#xff0c;可以分为7大类 工具类别 工具命令 工具作用 备注 开发类 pt-duplicate-key-checker 列出并删除重复的索引和外键 pt-online-schema-change 在线修改表结构 pt-query-advisor 分析查询语句&#xff0c;并给出建议&#x…

Docker 笔记(七)--打包软件生成镜像

目录 1. 背景2. 参考3. 文档3.1 使用docker container commit命令构建镜像3.1.1 [Docker官方文档-docker container commit](https://docs.docker.com/reference/cli/docker/container/commit/)Description&#xff08;概述&#xff09;Options&#xff08;选项&#xff09;Exa…

108、3D Gaussian Splatting for Real-Time Radiance Field Rendering

简介 官网 更少训练时间的同时实现最先进的视觉质量&#xff0c;能在1080p分辨率下实现高质量的实时(≥30 fps)新视图合成 NeRF使用隐式场景表示&#xff0c;体素&#xff0c;点云等属于显示建模方法&#xff0c;3DGS就是显示辐射场。它用3D高斯作为灵活高效的表示方法&…

android Fragment 生命周期 方法调用顺序

文章目录 Introlog 及结论代码 Intro 界面设计&#xff1a;点击左侧按钮&#xff0c;会将右侧 青色的RightFragment 替换成 黄色的AnotherRightFragment&#xff0c;而这两个 Fragment 的生命周期方法都会打印日志。 所以只要看执行结果中的日志&#xff0c;就可以知道 Fragme…

2021年XX省赛职业院校技能大赛”高职组 计算机网络应用赛项 网络构建模块竞赛真题

“2021年XX省赛职业院校技能大赛”高职组 计算机网络应用赛项 网络构建模块竞赛真题 目录 一&#xff0e;考试说明 1 二&#xff0e;模块B网络构建 2 &#xff08;一&#xff09;任务描述 2 &#xff08;二&#xff09;任务清单 9 一&#xff0e;考试说明 本模块比赛时间为…

服务消费微服务

文章目录 1.示意图2.环境搭建1.创建会员消费微服务模块2.删除不必要的两个文件3.检查父子模块的pom.xml文件1.子模块2.父模块 4.pom.xml 添加依赖&#xff08;刷新&#xff09;5.application.yml 配置监听端口和服务名6.com/sun/springcloud/MemberConsumerApplication.java 创…

nodejs+vue高校洗浴管理系统python-flask-django-php

高校洗浴管理系统采用数据库是MySQL。网站的搭建与开发采用了先进的nodejs进行编写&#xff0c;使用了express框架。该系统从两个对象&#xff1a;由管理员和学生来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对学生管理、浴室信息、浴室预约、预约…

【算法竞赛进阶指南】0x05 排序

0x05排序 排序基本算法 785. 快速排序 - AcWing题库姑且用这个评测。 1.选择、插入、冒泡 这类排序是基于比较的排序算法&#xff0c;时间复杂度为 选择排序 思路比较简单&#xff0c;就是每一次从后面选出最小的数字来与当前这个数字交换 #include<bits/stdc.h> co…

(一)基于IDEA的JAVA基础7

关系运算符 运算符 含义 范例 结果 等于 12 false &#xff01; 不等于 1&#xff01;2 true > 大于 1>2 false < 小于 …

【协议-HTTPS】

https https是在http协议的基础上&#xff0c;添加了SSL/TLS握手以及数据加密传输&#xff0c;也属于应用层协议。 httpshttp加密认证完整性保护 https交互图&#xff1a; HTTPS的整体过程分为证书验证和数据传输阶段&#xff1a; ① 证书验证阶段 浏览器发起 HTTPS 请求 服务…

如何通过idea搭建一个SpringBoot的Web项目(最基础版)

通过idea搭建一个SpringBoot的Web项目 文章目录 通过idea搭建一个SpringBoot的Web项目一、打开idea&#xff0c;找到 create new project二、创建方式三、配置项目依赖四、新建项目模块五、总结 一、打开idea&#xff0c;找到 create new project 方式1 方式2 二、创建方式 新…

如何使用PHP和RabbitMQ实现消息队列?

前言 今天我们来做个小试验&#xff0c;用PHP和RabbitMQ实现消息队列功能。 前期准备&#xff0c;需要安装好docker、docker-compose的运行环境。 如何使用docker部署php服务_php如何使用docker发布-CSDN博客 一、安装RabbitMQ 1、创建相关目录&#xff0c;执行如下命令。…

linux 通过nvm安装node

我的博客原文&#xff1a;linux 通过nvm安装node 前言 nvm是一个node版本控制的工具&#xff0c;他可以查看可以安装的node版本&#xff0c;安装node&#xff0c;以及切换node版本&#xff0c;传统的node安装&#xff0c;我们是下载压缩包&#xff0c;然后指定环境变量&…

医院预约挂号系统设计与实现|jsp+ Mysql+Java+ Tomcat(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

MySQL数据库基本操作(增删改查)与用户授权

前言 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理关系数据库系统的语言。SQL的设计目标是提供一种简单、直观的语言&#xff0c;使得用户可以通过编写SQL语句来处理他们想要的数据和操作。 目录 一、结构介绍 1. 查看信…

计算机软件安全

一、软件安全涉及的范围 1.1软件本身的安全保密 软件的本质与特征&#xff1a; 可移植性 寄生性 再生性 可激发性 攻击性 破坏性 …… 知识产权与软件盗版 软件商品交易形式不透明&#xff0c;方式多样&#xff0c;传统商标标识方法不适用&#xff1b; 盗版方法简捷…

Django Ajax

【一】Json 【1】介绍 JSON&#xff08;javascript object otaition&#xff09;是一种轻量级的数据交换格式JSON使用了Javascript的一部分语法来定义其数据格式&#xff0c;但Json是独立于语言的Json采用完全独立于语言的文本格式&#xff0c;使得Json成为理想的数据交互语言…