• SpringBoot + EasyExcel 实现表格数据导入


    1. 准备

    导入依赖

    <dependency>
    	<groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>3.0.5version>
        <scope>compilescope>
    dependency>
    
    <dependency>
        <groupId>org.projectlombokgroupId>
    	<artifactId>lombokartifactId>
    dependency>
    
    <dependency>
    	<groupId>mysqlgroupId>
    	<artifactId>mysql-connector-javaartifactId>
    	<scope>runtimescope>
    dependency>
    
    <dependency>
    	<groupId>org.mybatis.spring.bootgroupId>
    	<artifactId>mybatis-spring-boot-starterartifactId>
    	<version>1.3.1version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    本次示例使用的表格导入模板

    在这里插入图片描述

    2. 后端

    2.1 config

    2.1.1 自定义异常

    抛出该异常后停止解析

    public class ExcelAnalysisStopException extends ExcelAnalysisException {
    
        public ExcelAnalysisStopException(String message) {
            super(message);
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    数据转换异常错误

    @Getter
    @Setter
    public class ExcelDataConvertException extends RuntimeException {
    
        private Integer rowIndex;
        private Integer columnIndex;
    
        private CellData cellData;
        private ExcelContentProperty excelContentProperty;
    
        public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData) {
            super("第" + rowIndex + "行第" + columnIndex + "列,数据:" + cellData + "发生数据类型转换错误");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.1.2 监听器

    在监听接口中处理异常

    public abstract class AnalysisEventListener<T> implements ReadListener<T> {
        public AnalysisEventListener() {
        }
    
        public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
            this.invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context);
        }
    
        public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        }
    
        public void extra(CellExtra extra, AnalysisContext context) {
        }
    
        public void onException(Exception exception, AnalysisContext context) throws Exception {
            throw exception;
        }  //默认抛出异常
    
        public boolean hasNext(AnalysisContext context) {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    核心监听器类

    @Data
    public class ExcelListener extends AnalysisEventListener {
    	//  在下面进行补充
    	//  表格数据的解析和导入都是在这里进行
    	//  要注意的是,该监听器不支持交给 SpringBoot 管理,所以要将需要的类 new 出来,然后传进该监听器中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2 表格数据对应的实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Departments implements Serializable {
    
    	//  @ExcelIgnore 表示在进行表格数据导入解析时,忽略该属性
        @ExcelIgnore
        private Integer id;
    
        @ExcelProperty("院系名称")
        private String departmentName;
    
        private static final long serialVersionUID = 1L;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.2 controller

    @RestController
    @RequestMapping("/excel")
    @AllArgsConstructor
    public class ExcelImportController {
    
        private ExcelImportService excelImportService;
    
    	@PostMapping("/importData")
        public String importData(@RequestPart("file") MultipartFile file) {
            if (file == null || flag == null) {
                throw new Exception("文件为空!");
            }
            return excelImportService.importData(file);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.3 service

    /**
     * 表格导入 service 接口
     */
    public interface ExcelImportService {
        String importData(MultipartFile file);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    表格导入的前提,是要有一个规定的表头模板
    在解析时,我们要根据规定的表头去解析。如果提供的表格表头不正确直接抛出异常给前端

    @Service
    @AllArgsConstructor
    public class ExcelImportServiceImpl implements ExcelImportService {
        private final DepartmentsMapper departmentMapper;
    
        @Override
        public String importData(MultipartFile file) {
            String[] headList = {"院系名称"};  //  表头
            try {
                EasyExcel.read(file.getInputStream(), Departments.class, new ExcelListener(headList, departmentMapper)).sheet().doRead();
            } catch (Exception e) {
                return "导入失败";
            }
            return "导入成功";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.4 监听器逻辑补充

    @Data
    public class ExcelListener extends AnalysisEventListener {
    	
        private Integer num;  //  Excel行数
        private String message;  //  校验规则信息
        private String[] headList;  //  表头模板数据
        private static final int BATCH_COUNT = 30; //  每隔30条存数据库,然后清理list ,方便内存回收
        private List datas = new ArrayList<>();  //  数据集合
        private DepartmentsMapper departmentMapper;
    
    	public ExcelListener(String[] headList, DepartmentsMapper departmentMapper) {
            this.num = 0;
            this.message = "";
            this.headList = headList;
            this.departmentMapper = departmentMapper;
        }
    
    	/**
         * 读取表头
         * @param headMap 表头Map
         * @param context
         */
        @Override
        public void invokeHeadMap(Map headMap, AnalysisContext context) {
            if (context.readRowHolder().getRowIndex() == 0) {
                for(int i = 0; i < headList.length; i++) {
                    try {
                        if (!headMap.get(i).equals(headList[i])) {
                            datas.clear();
                            message = "上传模板与系统模板不匹配";
                            throw new ExcelAnalysisStopException(message);
                        }
                    } catch (Exception e) {
                        datas.clear();
                        message = "上传模板与系统模板不匹配";
                        throw new ExcelAnalysisStopException(message);
                    }
                }
            }
        }
    
    	/**
         * 每一条数据解析都会来调用
         * @param object 当前行的数据对象
         * @param analysisContext
         */
        @Override
        public void invoke(Object object, AnalysisContext analysisContext) {
            if (isNull(object)) {
                datas.add(object);  //  数据存储到list,供批量处理,或后续自己业务逻辑处理
                num++;
            }
    
            if (datas.size() >= BATCH_COUNT) {
                saveData();
                datas.clear();  //  清理存储在内存中的数据
            }
        }
    
    	/**
    	 * 本人自己定义的方法
         * 判断导入表格中的数据是否有为空值
         * @param object 要进行进行检查的对象
         * @return 不为空则为true,反之
         */
        private boolean isNull(Object object) {
            boolean isNull = true;
    
            //  自己业务的逻辑
    
            return isNull;
        }
    
    	/**
         * 所有数据解析完成都会来调用
         * @param analysisContext
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            if (num == 0) {
                datas.clear();
                message = "上传的表格数据为空或上传的表格数据无效";
                throw new ExcelAnalysisStopException(message);
            }
            saveData();  //  自定义的保存数据方法
            datas.clear();
        }
    
    	/**
         * 在转换异常 获取其他异常下会调用本接口,抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行
         * @param exception 出现的异常
         * @param context
         * @throws Exception
         */
        @Override
        public void onException(Exception exception, AnalysisContext context) throws Exception {
            datas.clear();
    
            ExcelDataConvertException excelDataConvertException = null;
            if (exception instanceof ExcelDataConvertException) {
                excelDataConvertException = (ExcelDataConvertException) exception;
            }
            if (null != excelDataConvertException) {
                int row = excelDataConvertException.getRowIndex() + 1;
                int col = excelDataConvertException.getColumnIndex() + 1;
                message = "第" + row + "行,第" + col + "列," +  "解析异常";
            }
    
            throw new ExcelAnalysisStopException(message);
        }
    
    	/**
         * 当出现模板数据异常时,结束往下解析,抛出异常
         * @param context
         * @return 布尔值
         */
        @Override
        public boolean hasNext(AnalysisContext context) {
            return true;
        }
    
    	/**
         * 插入数据到数据库
         * @return 受影响的行数
         */
    	private int saveData() {
            removeDuplicate(datas);  //  去重
            return departmentMapper.insertBatch(datas);
        }
    
    	/**
         * 移除List中重复的元素
         * @param list 原List集合
         * @return 去重后的List集合
         */
        private static List removeDuplicate(List list) {
            Set h = new HashSet(list);
            list.clear();
            list.addAll(h);
            return list;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    2.5 Mapper

    此处使用 Mybatis 来与数据库进行交互

    2.5.1 interface

    public interface DepartmentsMapper {
    
        int insertBatch(@Param("list") List<Departments> departmentsList);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.5.2 xml

    <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
    	insert into departments (department_name)
    	values
    	<foreach collection="list" item="item" index="index" separator=",">
              (#{item.departmentName, jdbcType=VARCHAR})
    	foreach>
    insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3. 前端

    前端的样例,大家可以参考我下面这一篇文章的第二章节,后续只要更改访问后台的链接即可

    SpringBoot + Vue + MinIO 实现文件上传:https://blog.csdn.net/wanzijy/article/details/127601558

    4. 不足之处

    上面虽然是可以实现表格数据的导入,但如果有人去细心观察过后台的日志就会发现,一些表格中的空白行也会进行解析

    虽然本人有对每一行数据进行判空的操作,但是如果可以知道他是空白行,那么就可以省去进行判空的时间,减少资源的损耗

    那时本人一直都没有琢磨出来,有懂的博主们,可以私信与我沟通交流下

  • 相关阅读:
    【CVE-2023-4357】Chrome-XXE 任意文件读取漏洞复现及原理解析
    Azkaban集群模式部署
    和为S的两个数字
    软件测试概念介绍 -- 小白入门必看
    微信小程序 springboot新冠疫苗预约系统#计算机毕业设计
    APM建设踩了哪些坑?去哪儿旅行分布式链路追踪系统实践
    初阶数据结构学习记录——둘 顺序表
    Java泛型
    新开普智慧校园系统RCE漏洞 [附POC]
    史上最全的neo4j图数据库教程:基于neo4j搭建金融风控图谱详细教程
  • 原文地址:https://blog.csdn.net/wanzijy/article/details/127524455