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~

最近更新

  1. TCP协议是安全的吗?

    2024-03-25 10:12:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-25 10:12:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-25 10:12:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-25 10:12:02       18 阅读

热门阅读

  1. 统计文件夹下所有文件的字数

    2024-03-25 10:12:02       20 阅读
  2. 手机IP地址如何更换

    2024-03-25 10:12:02       19 阅读
  3. 想注册滴滴司机驾龄不够怎么办?

    2024-03-25 10:12:02       14 阅读
  4. 测试缺陷定位的基本方法

    2024-03-25 10:12:02       18 阅读
  5. Spark—GraphX实战 ID Mapping

    2024-03-25 10:12:02       17 阅读
  6. 想注册滴滴司机驾龄不够怎么办?

    2024-03-25 10:12:02       16 阅读
  7. 10种常用排序算法简介

    2024-03-25 10:12:02       16 阅读
  8. 想注册滴滴司机驾龄不够怎么办?

    2024-03-25 10:12:02       17 阅读
  9. 【蓝桥杯3.23小白赛】(详解)

    2024-03-25 10:12:02       18 阅读
  10. 机器学习的步骤与方法

    2024-03-25 10:12:02       15 阅读
  11. 【ML】机器学习任务攻略 4

    2024-03-25 10:12:02       17 阅读