EasyExcel使用Validator进行读取校验,批量处理数据,返回处理异常的行以及列

EasyExcel导入存在合并单元格的Excel Excel表格 下面是Excel表格,成果简介前可以作为主表数据存储(存在合并),成果简介后的数据作为从表数据存储。 实现思路 通过EasyExcel读取Excel数据,用List<AwardsDetailField> list接收 因为POI/EasyExcel对合并单元格的数据读取一次,需要把为空的单元格也赋值,这是核心技术难点。 对数据校验、封装然后写入数据库 存在校验不通过的数据返回给前端,所有数据校验通过写入数据库。 相关代码 下面贴出的是通过EasyExcel读取Exce 阅读详情

EasyExcel使用Validator进行读取校验,批量处理数据,返回处理异常的行以及列

我们通常使用EasyExcel读取数据时,需要写大量的if-else或者断言来确保excel中的数据格式以及内容是否符合我们的需要.而EasyExcel目前尚不支持Validator的校验,(我已经提了好久的issues,一直没加这个功能) https://github.com/alibaba/easyexcel/issues/2250

所以本次是通过ReadListener来实现通过Validator进行校验数据,批量处理数据,返回错误行数

基础环境

  • jdk17
  • maven3.6+
  • springboot3.0+|springboot2.0+
  • easy-excel:3.2.0

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
</dependency>

自定义数据处理Listener

通常我们使用EasyExcel读取数据时,都需要自定义一 个ReadListener

EasyExcel读取到数据的时候,会调用invoke方法,读取完成之后会调用doAfterAllAnalysed方法

我们具体的实现就是当EasyExcel调用invoke方法时,此时将得到的数据通过Validator进行校验完成之后,然后统一汇总然后再去处理(比如插入mysql等操作)

如果读校验出现异常,将具体错误的数据格的行与列返回给前端,让用户重新编辑再次上传

具体实现

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.ListUtils;
import com.github.mpcloud.framework.core.consts.SuppressWarningsName;
import com.github.mpcloud.framework.core.utils.excel.exception.ExcelDataValidException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.groups.Default;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.Consumer;

/**
 * The type Abstract valid listener.
 *
 * @param <T> the type parameter
 * @author : Milo
 */
@SuppressWarnings(SuppressWarningsName.UNUSED)
public class AnalysisValidReadListener<T> extends AnalysisEventListener<T> {
    private static final Logger log = LoggerFactory.getLogger(AnalysisValidReadListener.class);

    private final Integer batch;
    private final Consumer<List<T>> consumer;
    private final List<T> buffer;
    private final Validator validator;
    private Map<String, Integer> fieldColumnMapping;

    /**
     * Instantiates a new Abstract valid listener.
     */
    public AnalysisValidReadListener(final Consumer<List<T>> consumer) {
        this(500, consumer);
    }

    /**
     * Instantiates a new Jdbc event listener.
     *
     * @param batch the batch
     */
    public AnalysisValidReadListener(final Integer batch, final Consumer<List<T>> consumer) {
        this(batch, consumer, Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(false)
                .buildValidatorFactory()
                .getValidator());
    }

    /**
     * Instantiates a new Jdbc event listener.
     *
     * @param batch     the batch
     * @param validator the validator
     */
    public AnalysisValidReadListener(final Integer batch, final Consumer<List<T>> consumer, final Validator validator) {
        this.buffer = ListUtils.newArrayListWithExpectedSize(batch);
        this.batch = batch;
        this.validator = validator;
        this.consumer = consumer;
    }

    @Override
    public void invoke(final T data, final AnalysisContext context) {
        this.validate(data, context);
        this.buffer.add(data);
        if (this.buffer.size() >= this.batch) {
            this.consumer.accept(this.buffer);
            this.buffer.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(final AnalysisContext context) {
        if (!this.buffer.isEmpty()) {
            this.consumer.accept(this.buffer);
            this.buffer.clear();
        }
    }

    /**
     * Valid.
     *
     * @param data    the data
     * @param context the context
     */
    protected final void validate(final T data, final AnalysisContext context) {
        final Optional<ConstraintViolation<T>> violationOptional = this.validator.validate(data, Default.class).stream().findFirst();
        if (violationOptional.isEmpty()) {
            return;
        }
        final ConstraintViolation<T> violation = violationOptional.get();
        final String field = violation.getPropertyPath().toString();
        final String message = violation.getMessage();
        final Object value = violation.getInvalidValue();
        final Integer rowIndex = context.readRowHolder().getRowIndex();
        final Integer columnIndex = this.getColumnIndex(field, context);
        if (log.isWarnEnabled()) {
            log.warn("Validate field:[{}],message:[{}],value:[{}]", field, message, value);
        }
        throw new ExcelDataValidException(rowIndex, columnIndex, message);
    }

    /**
     * 听过属性从context获取列号
     * @param field 属性名称
     * @param context excel上下文
     * @return 列号
     */
    private Integer getColumnIndex(final String field, final AnalysisContext context) {
        if (Objects.nonNull(this.fieldColumnMapping)) {
            return this.fieldColumnMapping.get(field);
        }
        synchronized (this) {
            if (Objects.nonNull(this.fieldColumnMapping)) {
                return this.fieldColumnMapping.get(field);
            }
            this.fieldColumnMapping = new HashMap<>(16);
            final Map<Integer, Head> excelFileHead = context.readSheetHolder().excelReadHeadProperty().getHeadMap();
            for (final Map.Entry<Integer, Head> entry : excelFileHead.entrySet()) {
                this.fieldColumnMapping.put(entry.getValue().getFieldName(), entry.getKey());
            }
            return this.fieldColumnMapping.get(field);
        }
    }
}

具体使用

目前就是使用自定义的Listener去读取Excel文件
具体代码

读取实体


import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

import java.io.Serializable;
import java.util.Objects;
import java.util.StringJoiner;

/**
 * @author : Milo
 * @description :
 */
public class Student implements Serializable {
    @NotEmpty
    private String username;
    @NotNull
    private Integer age;
    @Email
    private String email;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return Objects.equals(getUsername(), student.getUsername()) && Objects.equals(getAge(), student.getAge()) && Objects.equals(getEmail(), student.getEmail());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUsername(), getAge(), getEmail());
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", Student.class.getSimpleName() + "[", "]")
                .add("username='" + username + "'")
                .add("age=" + age)
                .add("email='" + email + "'")
                .toString();
    }
}

读取操作

注意:我这里的ReadMeta,ReadResult,是自定义的,这个可以根据你自己需要去实现
ReadMeta里面主要包含了一些数据,如数据流,实体映射的Excel类型,Excel密码(直接传入EasyExcelFactory也是可以的)


import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.github.mpcloud.framework.core.consts.Constant;
import com.github.mpcloud.framework.core.consts.SuppressWarningsName;
import com.github.mpcloud.framework.core.utils.excel.model.ReadMeta;
import com.github.mpcloud.framework.core.utils.excel.model.ReadResult;
import com.github.mpcloud.framework.core.utils.excel.model.WriteMeta;
import com.github.mpcloud.framework.core.utils.http.HttpMediaType;
import com.github.mpcloud.framework.core.utils.list.CollectionUtil;
import com.github.mpcloud.framework.core.utils.math.MathUtils;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

/**
 * The type Excel util.
 *
 * @author : Milo
 */
@SuppressWarnings(SuppressWarningsName.UNUSED)
public class ExcelUtil {
    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);

    private ExcelUtil() {
    }

    /**
     * Read excel read result.
     *
     * @param <T>      the type parameter
     * @param meta     the meta {@link ReadMeta <T>}
     * @param listener the listener {@link AnalysisEventListener}
     * @return the read result
     */
    public static <T extends Serializable> ReadResult readExcel(final ReadMeta<T> meta, final AnalysisEventListener<T> listener) {
        ExcelReader reader = null;
        final ReadResult result = new ReadResult(Boolean.TRUE);
        try {
            reader = EasyExcelFactory.read(meta.getExcelStream(), meta.getClazz(), listener)
                    .headRowNumber(meta.getHeadRowNumber())
                    .password(meta.getPassword())
                    .ignoreEmptyRow(Boolean.TRUE)
                    .autoCloseStream(Boolean.TRUE)
                    .build();
            reader.readAll();
        } catch (final ExcelDataConvertException ex) {
            result.setSuccess(Boolean.FALSE);
            result.setFailMessage(ex.getRowIndex(), ex.getColumnIndex(), ex.getMessage());
            log.error(result.getMessage(), ex.getCause());
        } catch (final Exception ex) {
            result.setSuccess(Boolean.FALSE);
        } finally {
        Optional.ofNullable(reader).ifPresent(ExcelReader::finish);
        }
        return result;
    }
}

使用

public static void main(String[] args) throws FileNotFoundException {
    ReadMeta<Student> readMeta = ReadMeta.<Student>builder().clazz(Student.class).excelStream(new FileInputStream("")).build();
    ExcelUtil.readExcel(readMeta, new AnalysisValidReadListener<>(500, demos -> {
        // insert batch 
        // 这里标识每读取到500条数据进行打印
        // 生产中可以换成500条数据进行数据库插入
        System.out.println(demos);
    }));
}

具体代码地址

gitee地址

掘金地址

CSDN地址

B站地址

我的博客

EasyExcel + Validation实现bean校验 业务中可能存在多种导入需求,我们可以创建一个通用listener,就不用一个导入excel需求就创建一个独立的listener了。的invoke方法来进读取的, 所以我们可自定义listener来重写Invoke方法,将bean校验逻辑放入即可。Easyexcel是通过。1.通用listener。2.Bean校验工具类。 阅读详情

相关推荐

Springboot 集成 easyexcel excel导入并验证模板

提示:文章如有错误的地方请指出,以免误人子弟! easyexcel 导入 文章目录easyexcel 导入一、导入maven jar包二、直接上代码 提示:以下是本篇文章正文内容,下面案例可供参考 一、导入maven jar包 maven 地址 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <versi.

Sunny0209的博客 2860

easyExcel实现导入,关于每数据处理异常信息返回到前端

文章目录前言一、EasyExcel二、背景及解决主要问题导入excel校验返回文件导出总结 前言 JAVA解析Excel工具EasyExcel的初次使用 easyExcel git地址:https://github.com/alibaba/easyexcel 一、EasyExcel Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Exc

旋转丶木马的博客 6406

EasyValidate优雅地校验提交数据完整性

主要介绍了EasyValidate优雅地校验提交数据完整性,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

EasyExcel实战:实现导入文件参数校验并记录异常信息

亲测使用Easy Excel实现文件导入对指定字段进参数校验完成入表处理并日志记录参数异常数据

weixin_43401380的博客 6043

Excel导入校验工具

基于poi,excel导入验证工具 1. 需求 ​ 项目中excel 导入导出框架是用的poi(新项目推荐使用easyexcel),excel导出比较简单,比较麻烦的是excel导入,因为几乎每个字段都必须校验是否合法,所有字段的校验都写在一起的话,代码会很乱,特别是字段很多,业务校验规则有比较复杂的时候,很容易出bug,后续也很难维护。excel导入,项目中还有一个需求是,把excel中所有校验不通过的错误信息一起提示出来,而不是检查到一个错误就终止读取,这样可以提高用户的效率。所以封装了一个校验和业务分

u013738122的博客 2060

EasyExcel导入数据校验(hibernate-validator

最近做项目,在使用EasyExcel数据导入的时候,没有找到什么好的数据校验方法,很多博客都是直接写代码校验,感觉很麻烦,因此思考能不能有什么简便的方法。大家都知道web项目,在controller层的方法上加@Validated注解,调用接口的时候会自动进参数校验,于是思考能不能通过同样的方法对EasyExcel导入的数据校验

xiajw的博客 5178

EasyExcel注解方式校验数据

EasyExcel注解方式校验数据 一个方便读取excel内容,且可以使用注解进内容验证的包装工具 easyexcel-wraper有哪些功能? 在easyexcel的基础上进封装,方便读取excel内容,避免在主业务代码中嵌入重复繁琐的样本代码 支持Hibernate-validator验证框架,可以使用诸如@NotBlank,@NotDuiplicate的注解 如何使用 1、新建一...

jtryyy的博客 1万+

SpringBoot自定义Validator以及EasyExcel导入数据的Validtor校验

SpringBoot自定义Validator以及EasyExcel导入数据的Validtor校验

qq_32650789的博客 585

EasyExcel+Validator简单实践

目录前言一、EasyExcel二、使用步骤1.引入库2.代码示例总结 前言 使用 阿里巴巴开源 easyExcel+Validator数据校验的一个简单实践,后续可根据自己需要进模块化封装 一、EasyExcel EasyExcel 是阿里巴巴开源的操作Excel的jar包,可以简单快速的实现excel的上传下载模板导入等等....... 二、使用步骤 1.引入库 代码如下(示例): <!-- easyExcel工具包 --> <dependency> <gr

u011706563的博客 2039

EasyExcel-数据解析添加注解验证---Validator工具使用注意

Validator工具使用注意

weixin_51402941的博客 519

EasyExcel 校验后导入

/</</</

qq56477643的博客 580

easyExcel导入失败提示用户第几有误并回滚数据

easyExcel导入失败提示用户第几有误并回滚数据

aklzx的博客 1814

easyExcel导入并且返回异常数据提示信息

easyExcel导入

qq_29075071的博客 6351

EasyExcel实现Excel解析并返回解析失败数据思路

EasyExcel实现Excel解析并返回解析失败数据思路 需求说明 项目中需要使用Excel文件上传,处理解析Excel表格数据落库,并返回处理失败的统计数据; 相关技术及插件 这里选择使用阿里开源的EasyExcel来做Excel表格数据的主要处理工具; 相关maven依赖暂不赘述,需要自百度,后期会补充上; 代码思路梳理 a) 参考官方文档编写监听器 官网示例:https://www.yuque.com/easyexcel/doc/read#5c70a7ac b) 自定义监

weixin_44794056的博客 4254

easyexcel读取excel数据返回

EasyExcel

weixin_40203707的博客 1794

EasyExcel如何返回业务处理中的错误信息

前言 又又又接手老项目 进改造… 之前poi写的excel导入会导致oom内存溢出,调了内存也没用 就用EasyExcel重写了下 基于原项目结构的写 确实好难受 一句话概括本文: 引用传递 开始 由于我需要基于原项目逻辑走 导入之后的各种校验需要根据service层的逻辑进数据校验(这里我要基于原项目结构的写,easyexcel数据校验注解具体看官方文档), 校验失败然后serv...

墨落青衫 1万+

java利用EasyExcel实现导入功能,并返回错误信息的所属

java利用EasyExcel实现导入功能,并返回错误信息的所属

weixin_45670589的博客 6433
上一篇: SpringBoot使用Gateway聚合Springdoc,Knife4j
陈年_风褛
博客等级 码龄8年 7粉丝 · 3原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值