一、前言
在项目中,某些情景下我们需要验证编码是否重复,账号是否重复,身份证号是否重复等...
而像验证这类代码如下:
那么有没有办法可以解决这类似的重复代码量呢?
我们可以通过自定义注解校验的方式去实现,如下 在实体类上面加上自定义的注解 @FieldRepeatValidator(field = "resources", message = "菜单编码重复!")
即可
下面就先来上代码吧~
二、实现
基本环境:
- javax.validation.validation-api
- 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
@RestControllerAdvice
public 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
Java 自定义注解 校验指定字段对应数据库内容重复的更多相关文章
-
Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)
Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性) 前言:由于前段时间忙于写接口,在接口中需要做很多的参数校验,本着简洁.高效的原则,便写了这个小工具供自己使用(内容 ...
-
java自定义注解实现前后台参数校验
2016.07.26 qq:992591601,欢迎交流 首先介绍些基本概念: Annotations(also known as metadata)provide a formalized way ...
-
如何让jpa 持久化时不校验指定字段
源文:https://www.toocruel.net/jpa-validate/ 怎么让jpa 持久化时不校验指定字段 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥 ...
-
java自定义注解类
一.前言 今天阅读帆哥代码的时候,看到了之前没有见过的新东西, 比如java自定义注解类,如何获取注解,如何反射内部类,this$0是什么意思? 于是乎,学习并整理了一下. 二.代码示例 import ...
-
Java自定义注解和运行时靠反射获取注解
转载:http://blog.csdn.net/bao19901210/article/details/17201173/ java自定义注解 Java注解是附加在代码中的一些元信息,用于一些工具在编 ...
-
java自定义注解注解方法、类、属性等等【转】
http://anole1982.iteye.com/blog/1450421 http://www.open-open.com/doc/view/51fe76de67214563b20b385320 ...
-
java自定义注解知识实例及SSH框架下,拦截器中无法获得java注解属性值的问题
一.java自定义注解相关知识 注解这东西是java语言本身就带有的功能特点,于struts,hibernate,spring这三个框架无关.使用得当特别方便.基于注解的xml文件配置方式也受到人们的 ...
-
JAVA自定义注解 ------ Annotation
日常开发工作中,合理的使用注解,可以简化代码编写以及使代码结构更加简单,下面记录下,JAVA自定义注解的开发过程. 定义注解声明类. 编写注解处理器(主要起作用部分). 使用注解. 相关知识点介绍, ...
-
Java自定义注解的实现
Java自定义注解的实现,总共三步(eg.@RandomlyThrowsException): 1.首先编写一个自定义注解@RandomlyThrowsException package com.gi ...
随机推荐
-
Servlet解决参数乱码问题
为什么会产生乱码? 之所以会产生乱码,是由于服务器端和客户端的编码方式不一致造成的.客户端与服务器端的交互过程中,存在着两次数据交换:第一次,客户端向服务器端发起请求,第二次数据交换,服务器端响应客户 ...
-
Node.js 安装与配置
引言: JavaScript是一种运行在浏览器的脚本,它简单,轻巧,易于编辑,这种脚本通常用于浏览器的前端编程,但是一位开发者Ryan有一天发现这种前端式的脚本语言可以运行在服务器上的时候,一场席卷全 ...
-
Java面向对象三大特点之多态
概念: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作,如图所示: 多态性是对象多种表现形式的体现,同一个事件发生在不同的对象上会产生不同的结果. ...
-
(转)优化tomcat,提高网站运行速度
网站优化方案: 网站优化有很多方面,这里我们先主要讲讲 tomcat优化.[主要针对tomcat6.0及以上版本] 1. 为jvm增加更多的内存,tomcat安装时,默认为126M,可以设置. To ...
-
cocos2d-x 2.1.4 使用create_project.py脚本创建项目+ant打包项目
1.创建项目:执行create_project.py脚本,进入Doc界面输入下面的命令: cd D:\cocos2d-x-2.1.4\cocos2d-x-2.1.4\tools\project-cre ...
-
Hadoop 中 IPC 的源码分析
最近开始看 Hadoop 的一些源码,展开hadoop的源码包,各个组件分得比较清楚,于是开始看一下 IPC 的一些源码. IPC模块,也就是进程间通信模块,如果是在不同的机器上,那就可以理解为 RP ...
-
CSS 不规则图形绘制
基础技能1 - 神奇的border 我们先来画一个长方形: .Rectangle { height: 100px; width: 200px; background: darkgray; border ...
-
Ansible playbook 批量修改服务器密码 先普通后root用户
fsckzy Ansible playbook 批量修改服务器密码 客户的需求:修改所有服务器密码,密码规则为Rfv5%+主机名后3位 背景:服务器有CentOS6.7,SuSE9.10.11,r ...
-
spring+mybatis框架搭建时遇到Mapped Statements collection does not contain value for...的错误
http://your233.iteye.com/blog/1563240 上面的博客总结了4个可能出现的问题,如下: 1.mapper.xml中没有加入namespace 2.mapper.xml中 ...
-
this 指向问题ES5
ES5中this的指针 按照this指针的优先级,列出下面常会遇到的四种情况,从上到下依次是优先级从高到低(后面会详细比较优先级). 函数是和new一起被调用的吗(new绑定)?如果是,this就是新 ...