Java自定义校验注解实现List、set集合字段唯一性校验

news2024/9/21 0:49:41

文章目录

  • 一: 使用场景
  • 二: 定义FieldUniqueValid注解
    • 2.1 @FieldUniqueValid
    • 2.2 注解说明
    • 2.3 @Constraint 注解介绍
    • 2.4 @FieldUniqueValid注解使用
  • 三:自定义FieldUniqueValidator校验类
    • 3.1 实现ConstraintValidator
    • 3.2 重写initialize方法
    • 3.3 重写isValid方法
    • 3.4 获取list集合重复数据的下标
    • 3.5 思路
    • 3.6 测试
      • 3.6.1 前端传递参数,需要进行唯一性校验的字段
      • 3.6.2 message提示

一: 使用场景

在开发过程中,前端给后端传递集合,并且需要保证集合的实体类中的某些字段必须是惟一的,不能重复。

传递的集合:

private List<User> userInfoList;

集合对应的实体类:

@Data
public class User {

    private int id;

    private String name;
    
    private String card;

}

如果我们要保证传递的name或者card必须是唯一的,不能重复,应该如何实现呢,此时可以通过自定义注解的方式实现。

二: 定义FieldUniqueValid注解

2.1 @FieldUniqueValid


/**
 * 该注解用于校验List集合实体类当中的某些字段的唯一性
 * <p>
 * 条件表达式支持使用"$parent."获取父节点属性(实体内不能使用除List集合外的其他集合类型,例如Set等)
 * <ul>
 * <li>标记在字段上:用于指定单个或多个字段,fields需填写</li>
 * </ul>
 * @author ikun
 * @date 2023.07.27
 */
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldUniqueValidator.class)
public @interface FieldUniqueValid {

    //需要进行唯一校验的字段
    String[] fieldsCode() default{};

    String[] fieldsName() default{};

    String message() default "[fieldName]列[index]行数据重复";

    //不能有默认值,报错:contains Constraint annotation, but the groups parameter default value is not the empty array
    Class<?>[] groups() default {};//在那种组中使用

    Class<? extends Payload>[] payload() default {};
}

2.2 注解说明

@Documented

@Document 是 java 在生成文档,是否显示注解的开关。

@Target(ElementType.FIELD)

ElementType.FIELD:该注解只能声明在一个类的字段前。

2.3 @Constraint 注解介绍

@Constraint注解是Java Bean Validation框架中的一个注解,用于自定义约束注解,即自定义校验规则。

通过在自定义注解上添加@Constraint注解,可以将该注解标记为一个自定义约束注解。同时,需要指定一个实现了ConstraintValidator接口的验证器类,用于验证该注解所标记的字段或参数是否符合自定义的校验规则。

@Constraint注解有以下属性:

  • validatedBy:用于指定实现了ConstraintValidator接口的验证器类。该属性的值是一个Class对象数组,可以指定多个验证器类。

  • message:用于指定当校验失败时,所返回的错误信息。可以使用占位符{},在校验器中使用具体的参数替换。

  • groups:用于指定分组,即根据不同的分组应用不同的校验规则。

  • payload:用于指定元数据,即可以通过该属性传递一些额外的验证信息。

使用@Constraint注解,可以通过自定义注解的方式,为字段或参数添加自定义的校验规则,并实现校验逻辑。这样,在进行参数校验时,可以方便地通过注解的方式来调用自定义的校验规则。

2.4 @FieldUniqueValid注解使用

@FieldUniqueValid(fieldsCode = {"name,card"}, fieldsName = {"姓名,身份证号"})
private List<User> userInfoList;
  • fieldsCode :需要校验的字段
  • fieldsName :校验字段对应的名称

三:自定义FieldUniqueValidator校验类

@Slf4j
public class FieldUniqueValidator implements ConstraintValidator<FieldUniqueValid, Iterable<?>> {

    private String[] fieldsCode;

    private String[] fieldsName;

    /**
     * 数据有重复的字段名称
     */
    private static final String FIELD_NAME= "[fieldName]";

    /**
     * 相同元素下标集合
     */
    private static final String INDEX = "[index]";

    /**
     * 初始化参数
     * @param constraintAnnotation 注解的值
     */
    @Override
    public void initialize(FieldUniqueValid constraintAnnotation) {
        fieldsCode = constraintAnnotation.fieldsCode();
        fieldsName = constraintAnnotation.fieldsName();
    }

    @Override
    public boolean isValid(Iterable<?> value, ConstraintValidatorContext context) {
        //如果没有配置校验字段信息,则直接通过
        if(fieldsCode.length == 0 && fieldsName.length == 0){
            return true;
        }
        if(fieldsCode.length != fieldsName.length){
            throw new RuntimeException("@FieldUniqueValid注解所对应的fieldsCode和fieldsName无法相互映射");
        }
        if(value == null){
            throw new RuntimeException("@FieldUniqueValid注解所在的集合为空,无法判断");
        }
        for (int i = 0; i < fieldsCode.length; i++) {
            List<Object> list = new ArrayList<>();
            Iterator<?> iterator = value.iterator();
            long index;
            for (index = 0; iterator.hasNext(); index++) {
                Object fieldValue = null;
                try {
                    //forceAccess = true,属性是私有的,需要设置为true打开权限
                    fieldValue = FieldUtils.readField(iterator.next(),fieldsCode[i],true);
                } catch (IllegalAccessException e) {
                    log.error(fieldsName[i] + "转化失败,无法进行校验", e);
                }
                list.add(fieldValue);
            }
            //去重后的总数
            long count = list.stream().distinct().count();
            //去重之前和去重以后进行比较
            if(count < index){
                //返回重复元素下标集合
                String sameIndex = getListSameIndex(list);
                String defaultConstraintViolation = context.getDefaultConstraintMessageTemplate();
                context.disableDefaultConstraintViolation();
                String convertedConstraintViolation = defaultConstraintViolation.replace(FIELD_NAME, fieldsName[i]).replace(INDEX, sameIndex);
                context.buildConstraintViolationWithTemplate(convertedConstraintViolation).addConstraintViolation();
                return false;
            }
        }
        return true;
    }


}

3.1 实现ConstraintValidator

ConstraintValidator<FieldUniqueValid, Iterable<?>>:

  • FieldUniqueValid:需要校验的注解,就是我们自定义的
  • Iterable<?>:前端传递list的类型,此时用Iterable是因为数据支持list和set集合

3.2 重写initialize方法

可以从onstraintAnnotation参数中获取fieldsCode、fieldsName里面的参数。主要作用就是将注解的参数进行初始化

3.3 重写isValid方法

Iterable<?> value, ConstraintValidatorContext context

  • value:可以获取到传递的集合数据
  • context:获取注解上的message信息

3.4 获取list集合重复数据的下标

/**
     * 集合【List】找出list中重复元素的下标(显示下标所在位置)
     * @param list
     */
    public static String getListSameIndex(List<?> list){
        List<Object> same = new ArrayList<>();
        List<?> collect = list.stream().distinct().collect(Collectors.toList());
        if(collect.size() == list.size()){
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i <collect.size(); i++) {
            for (int j = 0; j < list.size(); j++) {
                if (list.get(j).equals(collect.get(i))){
                    same.add(j+1);
                }
            }
            if (same.size() > 1){
                sb.append(same).append("、");
            }
            same.clear();
        }
        return sb.substring(0, sb.toString().lastIndexOf("、"));
    }

3.5 思路

首先获取到集合的数据,然后通过反射,用循环遍历获取到name字段的list数据,然后去重。将去重前后的list进行比较。如果长度变化了则说明有重复数据。此时返回false。然后我们我们通过getListSameIndex方法获取到list重复数据的下标然后替换[index]。

3.6 测试

3.6.1 前端传递参数,需要进行唯一性校验的字段

![在这里插入图片描述](https://img-blog.csdnimg.cn/e613f37d22554914940b3a162ed0916c.png

3.6.2 message提示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/835351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决mvn clean install遇到testng单元测试失败时打包也失败的问题

解决mvn clean install遇到testng单元测试失败时打包也失败的问题 看这个之前请先看这个 Jenkins执行Testng 比如我现在就有一个单元测试失败的项目 执行mvn clean install的时候就会报错 下面是我现在的pom.xml 但我们不希望这样&#xff0c;怎么办 <plugin><gr…

【Vs Code】如何配置保存cpolar所建立的隧道参数?

文章目录 &#x1f4cb; 前言1.如何配置保存cpolar所建立的隧道参数&#xff1f;2.本地安装 VS Code并修改隧道参数2.1 Visual studio Code 下载2.2 配置Visual studio Code 相关参数2.3 编辑 cpolar.yml 隧道参数文件2.3 修改website隧道参数 3. 检查 cpolar.yml 文件配置是否…

2023年 Java 面试八股文(20w字)

目录 第一章-Java基础篇 1、你是怎样理解OOP面向对象 难度系数&#xff1a;⭐ 2、重载与重写区别 难度系数&#xff1a;⭐ 3、接口与抽象类的区别 难度系数&#xff1a;⭐ 4、深拷贝与浅拷贝的理解 难度系数&#xff1a;⭐ 5、sleep和wait区别 难度系数&a…

苹果提交审核出现“您的 App 包含 NSUserTrackingUsageDescription...”解决办法

您的 App 包含 NSUserTrackingUsageDescription&#xff0c;这表示您将会请求追踪用户。要在 App 产品页上更新此信息&#xff0c;您必须注明哪些数据类型会追踪用户。如果此描述有误&#xff0c;请更新您的 App 二进制文件&#xff0c;并将新的构建版本上传到 App Store Conne…

飞凌嵌入式「国产」平台大盘点(一)瑞芯微系列

“国产化”一词正在被越来越多的提及&#xff0c;有着越来越高的关注度&#xff0c;飞凌嵌入式也已与多家国内芯片原厂联合推出了数款国产化智能平台。为了帮助大家快速认识飞凌嵌入式推出的各系列国产核心板产品&#xff0c;小编将以芯片品牌进行分类带大家一起盘点。 本篇文…

注释//TODO的作用

// TODO用来标记某处&#xff0c;表示该处含有待办事项&#xff08;尚未解决&#xff09;。其设计本意只是提醒开发者注意&#xff0c;除了代码高亮之外&#xff0c;还可以借助编辑器实现快速定位。 如何使用 直接在 双斜杠后面加 TODO 或者 todo 即可&#xff0c;大小写不敏感…

加拿大量子研究新动作!D-Wave与滑铁卢大学合作研究量子相干性

​ &#xff08;图片来源&#xff1a;网络&#xff09; D-Wave是量子计算系统、软件和服务的领导者&#xff0c;也是量子计算机的第一家供应商。近期&#xff0c;D-Wave宣布与滑铁卢大学量子计算研究所&#xff08;IQC&#xff09;达成两项新合作。他们为量子计算系统建立了关键…

【Spring Cloud 五】OpenFeign负载均衡

这里写目录标题 系列文章目录背景一、OpenFeign是什么Feign是什么Feign的局限性 OpenFeign是什么 二、为什么要有OpenFeign三、如何使用OpenFeign服务提供者order-servicepom文件yml配置文件启动类实体ParamController 服务消费者user-servicepom文件yml配置文件启动类接口类Us…

嵌入式该往哪个方向发展?

1. 你所在的城市嵌入式Linux岗位多吗&#xff1f;我觉得这是影响你做决定的另一个大问题。我们学嵌入式Linux这门技术&#xff0c;绝大部分人是为了从事相关的工作&#xff0c;而不是陶冶情操。但是根据火哥统计来看&#xff0c;嵌入式Linux的普遍薪资虽然高于单片机&#xff0…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(7 月 28 日论文合集)

文章目录 一、检测相关(11篇)1.1 Adaptive Segmentation Network for Scene Text Detection1.2 EFLNet: Enhancing Feature Learning for Infrared Small Target Detection1.3 MIM-OOD: Generative Masked Image Modelling for Out-of-Distribution Detection in Medical Image…

CSS中所有选择器详解

文章目录 一、基础选择器1.标签选择器2.类选择器3.id选择器4.通配符选择器 二、复合选择器1.交集选择器2.并集选择器 三、属性选择器1.[属性]2.[属性属性值]3.[属性^属性值]4.[属性$属性值]5.[属性*属性值] 四、关系选择器1.父亲>儿子2.祖先 后代3.兄弟4.兄~弟 五、伪类选择…

NTT DATA利用相干伊辛机模拟基因组组装和疾病治疗的潜力

​ &#xff08;图片来源&#xff1a;网络&#xff09; 7月20日&#xff0c;日本领先的IT服务提供商和行业咨询公司NTT DATA宣布完成了一个使用量子计算优化基因组组装过程的项目。这是量子计算应用于医疗保健和生命科学行业中的一个里程碑。 本项目通过比较量子和非量子计算方…

Ubuntu18.04 安装opencv 4.8.0教程(亲测可用)

1. 安装准备 安装前需要下载一些必须的依赖项。 不同版本opencv依赖会有不同&#xff0c;具体见官网opencv安装 sudo apt-get install build-essential sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-…

Elasticsearch官方测试数据导入

一、数据准备 百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1rPZBvH-J0367yQDg9qHiwQ?pwd7n5n 提取码&#xff1a;7n5n文档格式 {"index":{"_id":"1"}} {"account_number":1,"balance":39225,"firstnam…

(附代码)视觉激光雷达信息融合与联合标定

目录 一、引言 福利&#xff1a;文末有chat-gpt纯分享&#xff0c;无魔法&#xff0c;无限制 二、联合标定转换关系 三、相机标定 四、联合标定 五、视觉与点云信息融合 5.1 image2points 5.2 points2image 六、总结 一、引言 最近在为车辆添加障碍物检测模块&#xf…

【雕爷学编程】MicroPython动手做(31)——物联网之Easy IoT

1、物联网的诞生 美国计算机巨头微软(Microsoft)创办人、世界首富比尔盖茨&#xff0c;在1995年出版的《未来之路》一书中&#xff0c;提及“物物互联”。1998年麻省理工学院提出&#xff0c;当时被称作EPC系统的物联网构想。2005年11月&#xff0c;国际电信联盟发布《ITU互联网…

Nginx源码安装

文章目录 Nginx源码安装注安装pcre库安装openssl库创建用户及用户组安装编译环境解压文件检测环境重要目录检查配置服务启动脚本查看效果&#xff1a; Nginx源码安装 注 本实验基于RHEL73.10.0-327.el7.x86_64&#xff0c;尽量使用RHEL7或CentOS7。 安装pcre库 安装 pere 库…

(自控原理)线性系统的根轨迹法

目录 一、根轨迹法的基本概念 1、根轨迹概念 2、根轨迹方程 二、根轨迹绘制的基本法则 1、绘制根轨迹基本法则 三、系统性能的分析 1、闭环零点与时间响应 一、根轨迹法的基本概念 1、根轨迹概念 三大分析矫正方法&#xff1a;时域法、复域法(根轨迹法)、频域法 2、根…

【网络】数据链路层

目录 一、以太网 二、以太网帧格式 三、 MTU 1、MTU概念 2、 MTU对IP协议的影响 3、MTU对UDP协议的影响 4、 MTU对于TCP协议的影响 四、MAC地址 五、 ARP协议 1、ARP协议的作用 2、ARP协议的工作流程 3、ARP数据报的格式 4、中间人 数据链路层解决的&#xff0c;是…

mysql使用SUBSTRING_INDEX拆分字符串,获取省、市、县和详细现住址

mysql使用SUBSTRING_INDEX拆分字符串&#xff0c;获取省、市、县和详细现住址 一、如何把"江西-上饶市-广丰县-大南镇古村村张家82号"拆分为省、市、县和详细现住址二、mysql的解决办法 一、如何把"江西-上饶市-广丰县-大南镇古村村张家82号"拆分为省、市、…