本文使用mybatisplus版本:3.5.3.1
参考了两篇文章:
https://www.jianshu.com/p/6b6454073c89
https://www.cnblogs.com/xiaokangk/p/14208090.html
自己再实现的原因,mybatisplus版本升级了,包名也对应变化了。
增强实现的点:
1.增加子注解,实现多条件每个条件单独判充重
2.对于没有TableField的字段,用驼峰转下划线的字段
基本注解定义
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();
}
校验主类
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("获取字段的值报错");
}
}
}
这里自定义一个异常类CustomerValidateException
package vip.xiaonuo.common.validator;
public class CustomerValidateException extends RuntimeException{
public CustomerValidateException(String message) {
super(message);
}
}
这里自定义手动校验工具类,用于非传参场景使用
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());
}
}
注解加到类上。
使用@FieldRepeatValidator注解
@FieldRepeatValidator(fields = {"code"},message = "编号重复")
fields支持多个
@FieldRepeatValidator(fields = {"code","name"},message = "编号+名称重复")
使用@FieldRepeatValidators注解嵌套@FieldRepeatValidator注解
@FieldRepeatValidators(value = {
? ? ? ? @FieldRepeatValidator(fields = {"code"},message = "编号重复"),
? ? ? ? @FieldRepeatValidator(fields = {"name"},message = "名称重复"),
? ? ? ? @FieldRepeatValidator(fields = {"code","name"},message = "编号+名称重复")
})
工具类里强转的类型
?extends Model<T>
早期这里的版本是继承BaseEntity
实体insert/update前加手动校验代码
ValidateUtil.valid(userEntity);