java自定义注解实现数据库字段判重

发布时间:2023年12月23日

本文使用mybatisplus版本:3.5.3.1

参考了两篇文章:

https://www.jianshu.com/p/6b6454073c89

https://www.cnblogs.com/xiaokangk/p/14208090.html

自己再实现的原因,mybatisplus版本升级了,包名也对应变化了。

增强实现的点:

1.增加子注解,实现多条件每个条件单独判充重

2.对于没有TableField的字段,用驼峰转下划线的字段

一、代码实现如下

1.注解定义

基本注解定义

package vip.xiaonuo.common.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 *  <p> 自定义字段对应数据库内容重复校验 注解 </p>
 *
 * @author dq
 * @description :
 * @author : zhengqing
 * @date : 2019/9/10 9:32
 */
// 元注解: 给其他普通的标签进行解释说明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】
@Documented
/**
 * 指明生命周期:
 *      RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
 */
@Retention(RetentionPolicy.RUNTIME)
/**
 * 指定注解运用的地方:
 *      ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
 */
@Target({ElementType.TYPE})
@Constraint(validatedBy = FieldRepeatValidatorClass.class)
public @interface FieldRepeatValidator {
    /**
     * 需要校验的字段
     * @return
     */
    String[] fields() default {};

    /**
     * 排序
     * @return
     */
    int order() default 0;


    /**
     * 默认错误提示信息
     * @return
     */
    String message() default "字段内容重复!";

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

}

子注解定义

package vip.xiaonuo.common.validator;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface FieldRepeatValidators {

    FieldRepeatValidator[] value();
 
}

2.校验

校验主类

package vip.xiaonuo.common.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 *  <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[] field;
    private String message;

    @Override
    public void initialize(FieldRepeatValidator fieldRepeatValidator) {
        this.field = fieldRepeatValidator.fields();
        this.message = fieldRepeatValidator.message();
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        return new FieldRepeatValidatorUtils().fieldRepeat(field, o, message);
    }

}

校验工具类

package vip.xiaonuo.common.validator;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;

import java.lang.reflect.Field;
import java.util.*;

/**
 *  <p> 数据库字段内容重复判断处理工具类 </p>
 *
 * @author:  zhengqing <br/>
 * @date:  2019/9/10$ 9:28$ <br/>
 * @version:  <br/>
 */
@Slf4j
public class FieldRepeatValidatorUtils {

    /**
     * 实体类中id字段
     */
    private String idColumnName;

    /**
     * 实体类中id的值
     */
    private Object idColumnValue;
    /**
     * 校验数据 TODO 后期如果需要校验同个字段是否重复的话,将 `field` 做 , 或 - 分割... ;  如果id不唯一考虑传值过来判断 或 取fields第二个字段值拿id
     *
     * @param fields:校验字段
     * @param o:对象数据
     * @param message:回调到前端提示消息
     * @return: boolean
     */
    public boolean fieldRepeat(String[] fields, Object o, String message) {
        try {
            // 没有校验的值返回true
            if(fields != null && fields.length == 0){
                return true;
            }
            checkUpdateOrSave(o);
            checkRepeat(fields,o,message);
            return true;
        }catch (Exception e){
            String msg = "验证字段是否重复报错";
            log.error(msg,e);
            throw new CustomerValidateException(e.getMessage());
        }
    }

    /**
     * 通过传入的实体类中 @TableId 注解的值是否为空,来判断是更新还是保存
     * 将值id值和id列名赋值
     * id的值不为空 是更新 否则是插入
     * @param o 被注解修饰过的实体类
     * @return
     */
    public void checkUpdateOrSave(Object o) throws Exception{

        Field[] fields = getAllFields(o.getClass());
        for (Field f:fields) {
            // 设置私有属性可读
            f.setAccessible(true);
            if(f.isAnnotationPresent(TableId.class)){
                TableId tableId = f.getAnnotation(TableId.class);
                String value = tableId.value();
                if(StringUtils.isNotEmpty(value)){
                    idColumnName = value;
                }else{
                    //TableId 注解的value为空,则使用字段名驼峰转下划线
                    idColumnName = StringUtils.camelToUnderline(f.getName());
                }
                idColumnValue = f.get(o);
            }
        }
    }
    /**
     * 获取本类及其父类的属性的方法
     * @param clazz 当前类对象
     * @return 字段数组
     */
    private static Field[] getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null){
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }


    /**
     * 通过传入的字段值获取数据是否重复
     * @param fields
     * @param o
     * @param message
     * @return
     */
    public void checkRepeat(String [] fields,Object o,String message){
        Model model = (Model) o;
        QueryWrapper entityWrapper = new QueryWrapper();
        Map<String,Object> queryMap = getColumns(fields,o);
        Iterator<Map.Entry<String, Object>> it = queryMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Object> entry = it.next();
            entityWrapper.eq(entry.getKey(),entry.getValue());
        }
        if(idColumnValue != null){
            //更新的话,那条件就要排除自身
            entityWrapper.ne(idColumnName,idColumnValue);
        }
        List list = model.selectList(entityWrapper);
        if(list != null && list.size()>0){
            throw new CustomerValidateException(message);
        }
    }

    /**
     * 多条件判断唯一性,将我们的属性和值组装在map中,方便后续拼接条件
     * @param fields
     * @param o
     * @return
     */
    public Map<String,Object> getColumns(String [] fields,Object o){
        Field[] fieldList = getAllFields(o.getClass());
        Map<String,Object> map = new HashMap<>();
        for (Field f : fieldList) {
            // ② 设置对象中成员 属性private为可读
            f.setAccessible(true);
            // 判断字段是否包含在数组中,如果存在,则将它对应的列字段放入map中
            if(ArrayUtils.contains(fields,f.getName())){
                getMapData(map,f,o);
            }
        }
        return map;
    }

    /**
     * 得到查询条件
     * @param map  列字段
     * @param f 字段
     * @param o 传入的对象
     */
    private void getMapData( Map<String,Object> map,Field f,Object o){
        try {
            if(f.isAnnotationPresent(TableField.class)){
                TableField tableField = f.getAnnotation(TableField.class);
                Object val = f.get(o);
                map.put(tableField.value(),val);
            }else  {
                //没有 TableField 注解时,试用驼峰转下划线 生成对应字段
                String tableFieldValue = StringUtils.camelToUnderline(f.getName());
                Object val = f.get(o);
                map.put(tableFieldValue,val);

            }
        }catch (IllegalAccessException i){
            throw new CustomerValidateException("获取字段的值报错");
        }
    }

}

3.自定义异常

这里自定义一个异常类CustomerValidateException

package vip.xiaonuo.common.validator;

public class CustomerValidateException extends RuntimeException{
    public CustomerValidateException(String message) {
        super(message);
    }
}

4.手动校验工具类

这里自定义手动校验工具类,用于非传参场景使用

package vip.xiaonuo.common.validator;

import vip.xiaonuo.common.exception.CommonException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 校验工具类
 * 手动校验参数
 *
 */
public class ValidateUtil {
    /**
     * 校验器
     * @param t         参数
     * @param <T>       参数类型
     * @return
     */
    public static <T> List<String> valid(T t){
        Validator validatorFactory = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<T>> errors = new HashSet<>();
        try{
            errors = validatorFactory.validate(t);
        }catch (ValidationException e1 ){
            //这里e.getMessage() = HV000028: Unexpected exception during isValid call.
            throw new CommonException(e1.getCause().getMessage());
        }catch (CustomerValidateException e){
            throw new CommonException(e.getMessage());
        }
        return errors.stream().map(error -> error.getMessage()).collect(Collectors.toList());
    }
}

二、代码使用如下

注解加到类上。


1.单字段判断重复

使用@FieldRepeatValidator注解

@FieldRepeatValidator(fields = {"code"},message = "编号重复")


2.多字段判断重复

fields支持多个

@FieldRepeatValidator(fields = {"code","name"},message = "编号+名称重复")


3.子注解多组合判断重复

使用@FieldRepeatValidators注解嵌套@FieldRepeatValidator注解

@FieldRepeatValidators(value = {
? ? ? ? @FieldRepeatValidator(fields = {"code"},message = "编号重复"),
? ? ? ? @FieldRepeatValidator(fields = {"name"},message = "名称重复"),
? ? ? ? @FieldRepeatValidator(fields = {"code","name"},message = "编号+名称重复")
})


4.实体entity需要继承Model

工具类里强转的类型

?extends Model<T>

早期这里的版本是继承BaseEntity


5.手动校验工具类的使用

实体insert/update前加手动校验代码

ValidateUtil.valid(userEntity);

文章来源:https://blog.csdn.net/gsls200808/article/details/135169499
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。