Java 自定义注解 校验指定字段对应数据库内容重复_weixin_30696427的博客-CSDN博客

一、前言

在项目中,某些情景下我们需要验证编码是否重复,账号是否重复,身份证号是否重复等...
而像验证这类代码如下:
在这里插入图片描述
那么有没有办法可以解决这类似的重复代码量呢?

我们可以通过自定义注解校验的方式去实现,如下 在实体类上面加上自定义的注解 @FieldRepeatValidator(field = "resources", message = "菜单编码重复!") 即可
在这里插入图片描述
下面就先来上代码吧~

二、实现

基本环境:
  1. javax.validation.validation-api
  2. org.hibernate.hibernate-validator

在SpringBoot环境中已经自动包含在spring-boot-starter-web中了,如果因为版本导致没有,可去maven仓库搜索手动引入到项目中使用

小编的springboot版本为: 2.1.7

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>
[注:小编是基于MyBatis-Plus的架构下实现的,其他架构略不同,本文实现方式可做参考]

1、自定义注解 @FieldRepeatValidator

// 元注解: 给其他普通的标签进行解释说明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】@Documented/** * 指明生命周期: *      RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 *      RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 *      RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 */@Retention(RetentionPolicy.RUNTIME)/** * 指定注解运用的地方: *      ElementType.ANNOTATION_TYPE 可以给一个注解进行注解 *      ElementType.CONSTRUCTOR 可以给构造方法进行注解 *      ElementType.FIELD 可以给属性进行注解 *      ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 *      ElementType.METHOD 可以给方法进行注解 *      ElementType.PACKAGE 可以给一个包进行注解 *      ElementType.PARAMETER 可以给一个方法内的参数进行注解 *      ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举 */@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})@Constraint(validatedBy = FieldRepeatValidatorClass.class)//@Repeatable(LinkVals.class)(可重复注解同一字段,或者类,java1.8后支持)public @interface FieldRepeatValidator {     /**     * 实体类id字段 - 默认为id (该值可无)     * @return     */    String id() default "id";;     /**     * 注解属性 - 对应校验字段     * @return     */    String field();     /**     * 默认错误提示信息     * @return     */    String message() default "字段内容重复!";     Class<?>[] groups() default {};    Class<? extends Payload>[]  payload() default {}; }

2、@FieldRepeatValidator注解接口实现类

/** *  <p> FieldRepeatValidator注解接口实现类 </p> * * @description : *        技巧01:必须实现ConstraintValidator接口 *     技巧02:实现了ConstraintValidator接口后即使不进行Bean配置,spring也会将这个类进行Bean管理 *     技巧03:可以在实现了ConstraintValidator接口的类中依赖注入其它Bean *     技巧04:实现了ConstraintValidator接口后必须重写 initialize 和 isValid 这两个方法; *              initialize 方法主要来进行初始化,通常用来获取自定义注解的属性值; *              isValid 方法主要进行校验逻辑,返回true表示校验通过,返回false表示校验失败,通常根据注解属性值和实体类属性值进行校验判断 [Object:校验字段的属性值] * @author : zhengqing * @date : 2019/9/10 9:22 */public class FieldRepeatValidatorClass implements ConstraintValidator<FieldRepeatValidator, Object> {     private String id;    private String field;    private String message;     @Override    public void initialize(FieldRepeatValidator fieldRepeatValidator) {        this.id = fieldRepeatValidator.id();        this.field = fieldRepeatValidator.field();        this.message = fieldRepeatValidator.message();    }     @Override    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {        return FieldRepeatValidatorUtils.fieldRepeat(id, field, o, message);    } }

3、数据库字段内容重复判断处理工具类

public class FieldRepeatValidatorUtils {     /**     * 实体类id字段     */    private static String id;    /**     * 实体类id字段值     */    private static Integer idValue;    /**     * 校验字段     */    private static String field;    /**     * 校验字段值 - 字符串、数字、对象...     */    private static Object fieldValue;    /**     * 校验字段 - 对应数据库字段     */    private static String db_field;    /**     * 实体类对象值     */    private static Object object;     /**     * 校验数据 TODO 后期如果需要校验同个字段是否重复的话,将 `field` 做 , 或 - 分割... ;  如果id不唯一考虑传值过来判断 或 取fields第二个字段值拿id     *     * @param field:校验字段     * @param object:对象数据     * @param message:回调到前端提示消息     * @return: boolean     */    public static boolean fieldRepeat(String id, String field, Object object, String message) {        // 使用Class类的中静态forName()方法获得与字符串对应的Class对象 ; className: 必须是接口或者类的名字        // 静态方法forName()调用 启动类加载器 -> 加载某个类xx -> 实例化 ----> 从而达到降耦 更灵活//        Object object = Class.forName(className).newInstance();         FieldRepeatValidatorUtils.id = id;        FieldRepeatValidatorUtils.field = field;        FieldRepeatValidatorUtils.object = object;        getFieldValue();         // ⑦ 校验字段内容是否重复        // 工厂模式 + ar动态语法        BaseEntity entity = (BaseEntity) object;//        List list = entity.selectPage( new Page<>( 1,1 ), new EntityWrapper().eq( field, fieldValue ) ).getRecords();        List list = entity.selectList( new EntityWrapper().eq( db_field, fieldValue ) );        // 如果数据重复返回false -> 再返回自定义错误消息到前端        if ( idValue == null ){            if ( !CollectionUtils.isEmpty( list ) ){                throw new MyException( message );            }        } else {            if ( !CollectionUtils.isEmpty( list ) ){                // fieldValueNew:前端输入字段值                Object fieldValueNew = fieldValue;                FieldRepeatValidatorUtils.object = entity.selectById( idValue );                // 获取该id所在对象的校验字段值 - 旧数据                getFieldValue();                if ( !fieldValueNew.equals( fieldValue ) || list.size() > 1 ){                    throw new MyException( message );                }            }        }        return true;    }     /**     * 获取id、校验字段值     */    public static void getFieldValue(){        // ① 获取所有的字段        Field[] fields = object.getClass().getDeclaredFields();        for (Field f : fields) {            // ② 设置对象中成员 属性private为可读            f.setAccessible(true);            // ③ 判断字段注解是否存在            if ( f.isAnnotationPresent(ApiModelProperty.class) ) {                // ④ 如果存在则获取该注解对应的字段,并判断是否与我们要校验的字段一致                if ( f.getName().equals( field ) ){                    try {                        // ⑤ 如果一致则获取其属性值                        fieldValue = f.get(object);                        // ⑥ 获取该校验字段对应的数据库字段属性  目的: 给 mybatis-plus 做ar查询使用                        TableField annotation = f.getAnnotation(TableField.class);                        db_field = annotation.value();                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }                // ⑦ 获取id值 -> 作用:判断是插入还是更新操作                if ( id.equals( f.getName() ) ){                    try {                        idValue = (Integer) f.get(object);                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }    } }

4、全局异常处理

作用:让校验生效,即参数校验时如果不合法就会抛出异常,我们就可以在全局异常中捕获拦截到,然后进行逻辑处理之后再返回给前端
@Slf4j@RestControllerAdvicepublic class MyGlobalExceptionHandler {     private static final Logger LOG = LoggerFactory.getLogger(MyGlobalExceptionHandler.class);     /**     * 自定义异常处理     */    @ExceptionHandler(value = MyException.class)    public ApiResult myException(MyException be) {        log.error("自定义异常:", be);        if(be.getCode() != null){            return ApiResult.fail(be.getCode(), be.getMessage());        }        return ApiResult.fail( be.getMessage() );    }     // 参数校验异常处理 ===========================================================================    // MethodArgumentNotValidException是springBoot中进行绑定参数校验时的异常,需要在springBoot中处理,其他需要处理ConstraintViolationException异常进行处理.     /**     * 方法参数校验     */    @ExceptionHandler(MethodArgumentNotValidException.class)    public ApiResult handleMethodArgumentNotValidException( MethodArgumentNotValidException e ) {        log.error( "方法参数校验:" + e.getMessage(), e );        return ApiResult.fail( e.getBindingResult().getFieldError().getDefaultMessage() );    }     /**     * ValidationException     */    @ExceptionHandler(ValidationException.class)    public ApiResult handleValidationException(ValidationException e) {        log.error( "ValidationException:", e );        return ApiResult.fail( e.getCause().getMessage() );    }     /**     * ConstraintViolationException     */    @ExceptionHandler(ConstraintViolationException.class)    public ApiResult handleConstraintViolationException(ConstraintViolationException e) {        log.error( "ValidationException:" + e.getMessage(), e );        return ApiResult.fail( e.getMessage() );    }    }

其中自定义异常处理代码如下:

public class MyException extends RuntimeException {     /**     * 异常状态码     */    private Integer code;     public MyException(Throwable cause) {        super(cause);    }     public MyException(String message) {        super(message);    }     public MyException(Integer code, String message) {        super(message);        this.code = code;    }     public MyException(String message, Throwable cause) {        super(message, cause);    }     public Integer getCode() {        return code;    } }

三、@FieldRepeatValidator注解使用举例

1、在实体类上加上如下代码

@FieldRepeatValidator(field = "resources", message = "菜单编码重复!")public class Menu extends BaseEntity { ... }

2、在controller层的方法中加上@Validated 注解即可!

@PostMapping(value = "/save", produces = "application/json;charset=utf-8")    @ApiOperation(value = "保存菜单 ", httpMethod = "POST", response = ApiResult.class)    public ApiResult save(@RequestBody @Validated Menu input) {        Integer id = menuService.save(input);        // 更新权限        shiroService.updatePermission(shiroFilterFactoryBean, null, false);        return ApiResult.ok("保存菜单成功", id);}

四、一些可直接使用的原生注解

下面的这些原生注解 百度一下,就会发现发现有很多,很简单就不多说了
@Null   必须为null@NotNull    必须不为 null@AssertTrue 必须为 true ,支持boolean、Boolean@AssertFalse    必须为 false ,支持boolean、Boolean@Min(value) 值必须小于value,支持BigDecimal、BigInteger,byte、shot、int、long及其包装类@Max(value) 值必须大于value,支持BigDecimal、BigInteger,byte、shot、int、long及其包装类@DecimalMin(value)  值必须小于value,支持BigDecimal、BigInteger、CharSequence,byte、shot、int、long及其包装类@DecimalMax(value)  值必须大于value,支持BigDecimal、BigInteger、CharSequence,byte、shot、int、long及其包装类@Size(max=, min=)   支持CharSequence、Collection、Map、Array@Digits (integer, fraction) 必须是一个数字@Negative   必须是一个负数@NegativeOrZero 必须是一个负数或0@Positive   必须是一个正数@PositiveOrZero 必须是个正数或0@Past   必须是一个过去的日期@PastOrPresent  必须是一个过去的或当前的日期@Future 必须是一个将来的日期@FutureOrPresent    必须是一个未来的或当前的日期@Pattern(regex=,flag=)  必须符合指定的正则表达式@NotBlank(message =)    必须是一个非空字符串@Email  必须是电子邮箱地址@NotEmpty   被注释的字符串的必须非空... ... ... 

五、总结

这里简单说下小编的实现思路吧
首先我们自定义一个注解,放在字段或者上,目的:通过反射获取其值,然后拿到值我们就可以进行一系列自己的业务操作了,比如更具字段属性和属性值查询到相应的数据库数据,然后进行校验,如果不符合自己的逻辑,我们就抛出一个异常交给全局统一异常类处理错误信息,最后返回给前端做处理,大体思路就是这样,实现起来很简单,代码中该有的注释都有,相信不会太难理解

    • *

最后再给出小编的源码让大家作参考吧

案例Demo

原网址: 访问
创建于: 2021-08-26 17:41:17
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论