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

EasyExcel自定义注解进阶:实现动态必填校验与分组验证 本文深入探讨了EasyExcel自定义注解的高级应用,重点介绍了如何实现动态必填校验与分组验证。通过改造基础注解、设计动态校验逻辑和分组校验实现,解决了不同业务场景下Excel导入字段校验的灵活性问题,大幅提升了开发效率和系统适应性。 阅读详情

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地址

掘金地址

优快云地址

B站地址

我的博客

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

相关推荐

基于 EasyExcel 的通用 Excel 导入框架设计:注解驱动 + 双管线两段式,从踩坑到性能优化

本文详细复盘了一个政企项目 Excel 导入框架的演进过程。针对传统实现中存在的性能浪费(每行重复反射扫描注解)、字典校验复杂度爆炸、策略重复判断等问题,设计了一套基于注解驱动、启动期元数据缓存、校验/转换双管线两段式设计的通用导入框架。 核心优化包括: 1)启动期缓存所有注解元数据,运行期零反射; 2)校验和转换采用两段式策略,将批次级不变量前置计算; 3)字典校验和转换通过预建索引将复杂度从 O(N×F×D) 降为 O(1); 4)支持自定义注解扩展点,遵循开闭原则。

qq_38039952的博客 389

EasyExcel导入存在合并单元格的Excel

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

我可能在扯淡 1万+

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

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

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

旋转丶木马的博客 6416

Python量化交易入门:天勤量化TqSdk保姆级教程(含避坑指南)

本文提供Python量化交易入门指南,重点介绍天勤量化TqSdk的使用方法,包括环境配置、核心API实战、策略开发技巧及实盘部署。教程涵盖中国期货市场,帮助个人投资者快速掌握量化交易技术,避开常见陷阱。

weixin_29065791的博客 834

从UART到RS485:手把手教你用SIT3485E收发器搞定工业级通信(含原理图与PCB布局要点)

本文详细解析了从UART到RS485的工业级通信转换,重点介绍了SIT3485E收发器的原理图设计、PCB布局要点及抗干扰技巧。通过差分信号传输和优化电路设计,实现长距离、多设备稳定通信,适用于工业自动化等复杂环境。

weixin_29169505的博客 349

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

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

weixin_43401380的博客 6056

Ubuntu 24.04 部署 EMQX 5.8 开源版MQTT服务器:从零搭建到实战应用

本文详细指导如何在Ubuntu 24.04系统上部署EMQX 5.8开源版MQTT服务器。内容涵盖从环境准备、安装Erlang、下载安装包到启动服务的完整步骤,并提供了验证服务、配置网络防火墙以及通过WebSocket工具进行基础消息收发的实战方法,帮助用户快速搭建稳定、免费的物联网消息中间件。

weixin_29209825的博客 293

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

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

aklzx的博客 1819

Etcd教程 — 第三章 Etcd集群动态发现、DNS发现

Etcd教程 — 第三章 Etcd集群动态发现、DNS发现

Mr_XiMu的博客 2819

一文读懂资管系统里的“清算路径”:钱和券的“导航地图”

简单来说,清算路径就是给每一笔交易预设好的“导航路线”。钱从哪个银行账户出?通过哪家证券公司去买?用哪个股东账户去登记?买回来的股票记在谁的名下?“清算路径”就是把这一连串问题的答案提前配置好。它连接了银行托管户(钱的源头)、券商(交易的中介)和交易所(买卖的市场)。没有配好这条路,交易就无法闭环。清算路径是金融基础设施的缩影。资金如何安全地从银行流向市场;资产如何合法地归属到产品名下;交易数据如何在不同系统间流转。它是资管系统能够日复一日、准确无误地处理亿万级交易的基石。

m0_37607945的博客 236

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

easyExcel导入

qq_29075071的博客 6373

MarkDown简介(深度解析MarkDown用法)

欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 UML序列图和流程图 离线写博客 导入导出Markdown文件 丰富的快捷键 快捷键 加粗 Ctrl + B 斜体 Ctrl + I...

酷酷的旭 7341

Excel导入校验工具

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

u013738122的博客 2064

Ubuntu20.04安装Eigen3.3.7、模板类Sophus和Ceres

Sophus的使用必须依赖3.3以上的Eigen版本。如果版本太低,Sophus库将无法安装使用。

qq_62964142的博客 3422

uniapp H5刷新页面后返回按钮失效问题完美解决方案

这个问题其实很隐蔽,尤其是uniapp H5 场景下刷新导致栈清理,很多开发者一开始都懵掉了。uniapp 内部对「页面栈只有一个」时的返回逻辑是reLaunch;所以只要在这个分支里替换为自己的逻辑(比如),就能顺利实现预期。目前我在项目里已经跑通了,暂时没发现问题。不得不说,知道源码里这个逻辑后,真的是醍醐灌顶,只能膜拜框架大佬了 🤯。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 524

EasyExcel导入数据校验(hibernate-validator)

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

xiajw的博客 5196

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

EasyExcel注解方式校验数据行

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

jtryyy的博客 1万+

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

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

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

qq_32650789的博客 589

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

EasyExcel+Validator简单实践

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

u011706563的博客 2044

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

Validator工具使用注意

weixin_51402941的博客 520

EasyExcel + Validation实现bean校验

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

adminstritor的博客 1010

EasyExcel 校验后导入

/</</</

qq56477643的博客 582

EasyExcel通用导入 | 简单封装

之前做了好几个导入,用EasyExcel每次都要定义监听器去处理,就想能不能做个通用的方式,如下。

假人一个的博客 1062

EasyExcel获取@Valid注解中的校验信息工具类

easyExcel

焱童鞋 1303

基于注解的读取excel的工具包

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

290
上一篇: SpringBoot使用Gateway聚合Springdoc,Knife4j
陈年_风褛
博客等级 码龄8年 7粉丝 3原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值