SpringBoot + EasyExcel 实现表格数据导入

1. 准备

导入依赖

<dependency>
	<groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.1</version>
</dependency>

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

在这里插入图片描述

2. 后端

2.1 config

2.1.1 自定义异常

抛出该异常后停止解析

public class ExcelAnalysisStopException extends ExcelAnalysisException {

    public ExcelAnalysisStopException(String message) {
        super(message);
    }
    
}

数据转换异常错误

@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 + "发生数据类型转换错误");
    }

}

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;
    }
}

核心监听器类

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

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;
}

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);
    }
}

2.3 service

/**
 * 表格导入 service 接口
 */
public interface ExcelImportService {
    String importData(MultipartFile file);
}

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

@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 "导入成功";
    }
}

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;
    }

}

2.5 Mapper

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

2.5.1 interface

public interface DepartmentsMapper {

    int insertBatch(@Param("list") List<Departments> departmentsList);

}

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>

3. 前端

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

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

4. 不足之处

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

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LF3_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值