数据校验 JSR303校验

2023-10-29 10:10
文章标签 数据 校验 jsr303

本文主要是介绍数据校验 JSR303校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1. 校验需求 

2. 校验方法

2.1 拼接式校验

2.1.1 定义

2.1.2 举例

2.1.3 缺点

2.2 声明式校验 JSR303

2.2.1 定义

2.2.2 相关注解

2.2.3 Hibernate Validator 扩展注解

2.2.4 基本使用

2.2.5 分组校验(多场景的复杂校验)和自定义校验


1. 校验需求 

        对前端传来入参做数据合法性检查。

2. 校验方法

2.1 拼接式校验

2.1.1 定义

        在业务成对request中的参数一个个做非空判断。

2.1.2 举例

        Stu.java

@Data
@EqualsAndHashCode(callSuper = false)
public class Stu implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id",type = IdType.AUTO)private int id;private String name;private int age;private String gender;private String address;private Date birth;
}

        StuServiceImpl.java

@Service
public class StuServiceImpl extends ServiceImpl<StuMapper, Stu> implements StuServiceI {@Overridepublic Result add(Stu stu) {if(stu.getName()==null){return Result.fail("学生名不能为空");}if(stu.getAddress()==null){return Result.fail("学生地址不能为空");}this.save(stu);return Result.success();}
}

2.1.3 缺点

        如果实体类字段很多,不易维护,给业务层增加负担。

2.2 声明式校验 JSR303

2.2.1 定义

        声明式校验,也称JSR303校验。

        JRS303是Java为Bean数据合法性校验提供的标准框架,JSR303通过在Bean属性上标注类似于@NotNull、@NotBlank实现数据合法性校验。

2.2.2 相关注解

注解功能
@Null验证对象是否为 null
@NotNull验证对象是否不为 null
@AssertTrue验证 Boolean 对象是否为 true
@AssertTrue验证 Boolean 对象是否为 false
@Max(value)验证 Number 和 String 对象是否小于等于指定值
@Min(value)验证 Number 和 String 对象是否大于等于指定值
@DecimalMax(value)验证注解的元素值小于等于 @DecimalMax 指定的 value 值
@DecimalMin(value)验证注解的元素值大于等于 @DecimalMin 指定的 value 值
@Digits(integer,fraction)验证字符串是否符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度
@Size(min,max)验证对象长度是否在给定的范围内
@Past验证 Date 和 Calendar 对象是否在当前时间之前
@Future验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern验证 String 对象是否符合正则表达式的规则
@NotBlank检查字符串是不是 Null,被 Trim 的长度是否大于0,只对字符串,且会去掉前后空格
@URL验证是否是合法的 url
@CreditCardNumber验证是否是合法的信用卡号

2.2.3 Hibernate Validator 扩展注解

注解功能
@Email验证是否是合法的邮箱
@Length(min,max)验证字符串的长度必须在指定范围内
@NotEmpty检查元素是否为 Null 或 Empty
@Range(min,max,message)验证属性值必须在合适的范围内

2.2.4 基本使用

2.2.4.1 pom.xml

  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

注意:validation 启动依赖里 包含jakarta:el:3.0.4.jar 和hibernate-validator:6.1.7.Final.jar

2.2.4.2 实体类Stu.java

@Data
@EqualsAndHashCode(callSuper = false)
public class Stu implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id",type = IdType.AUTO)private int id;@NotBlank(message = "姓名不能为空")@Length(max = 20, message = "用户名称的长度不能超过20")private String name;private int age;private String gender;@NotBlank(message = "性别不能为空")private String address;private Date birth;
}

2.2.4.3 控制层StuController.java

        使用@Valid注解。

        如果想进行分组校验可使用@Validated注解,这里不做演示。

@RestController
@RequestMapping("/stu")
public class StuController {@Resourceprivate StuServiceI stuService;@PostMappingpublic Result add(@Valid @RequestBody Stu stu){stuService.save(stu);return Result.success("新增成功");}
}

2.2.4.4 全局异常处理ExceptionCatch.java

        数据合法校验的异常是MethodArgumentNotValidException,多个数据合法校验异常,只提示用户第一个。

@RestControllerAdvice
public class ExceptionCatch {private static ImmutableMap.Builder<Class<? extends Exception>,ExceptionResult> builder = ImmutableMap.builder();/*** 自定义异常处理* @param e* @return*/@ExceptionHandler(CustomException.class)public ResponseResult handlerRuntimeException(CustomException e){ExceptionResult result = e.getExceptionResult();return ResponseResult.fail(result.retCode(),result.retMsg());}@ExceptionHandler(value= MethodArgumentNotValidException.class)public ResponseResult validateException(MethodArgumentNotValidException e){BindingResult bindingResult=e.getBindingResult();Map<String,Object> map=new HashMap<>();List<FieldError> fieldErrors = bindingResult.getFieldErrors();FieldError fieldError = fieldErrors.get(0);map.put("field",fieldError.getField());map.put("msg",fieldError.getDefaultMessage());return ResponseResult.fail("数据校验异常",map);}/*** 其他异常处理*          除运行时异常以外的异常* @param e* @return*/@ExceptionHandler(Exception.class)public ResponseResult handlerException(Exception e){ExceptionResult result = builder.build().get(e.getClass());if(ObjectUtil.isNotNull(result)){return ResponseResult.fail(result.retCode(),result.retMsg());}return ResponseResult.fail(e.getMessage());}
}

2.2.4.5 测试

2.2.5 分组校验(多场景的复杂校验)和自定义校验

项目中一般使用分组校验或自定义校验,这里更换实体类和优化统一异常处理。

1. 分组校验(多场景的复杂校验)

        新增、修改等接口想要校验的入参不同,可以通过分组进行校验。

(1)引入相关依赖 pom.xml

<!-- 数据校验 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

(2)返回前端数据的相关类

/*** 返回数据** @author wen*/
public class R extends HashMap<String, Object> {private static final long serialVersionUID = 1L;public R() {put("code", 0);put("msg", "success");}public static R error() {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");}public static R error(String msg) {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);}public static R error(int code, String msg) {R r = new R();r.put("code", code);r.put("msg", msg);return r;}public static R ok(String msg) {R r = new R();r.put("msg", msg);return r;}public static R ok(Map<String, Object> map) {R r = new R();r.putAll(map);return r;}public static R ok() {return new R();}@Overridepublic R put(String key, Object value) {super.put(key, value);return this;}
}

(3) 统一处理的全局异常、异常消息枚举类

@Slf4j
@RestControllerAdvice(basePackages = "com.wen.product.controller")
public class ExceptionControllerAdvice {@ExceptionHandler(MethodArgumentNotValidException.class)public R handlerValidException(MethodArgumentNotValidException e){log.error("数据校验异常:{},异常类型:{}",e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError)->{errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());});return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(), BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data",errorMap);}@ExceptionHandler(Throwable.class)public R handlerException(Throwable throwable){return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg());}
}
public enum BizCodeEnum {UNKNOW_EXCEPTION(10000,"系统未知异常"),VAILD_EXCEPTION(10001,"参数格式校验异常");private int code;private String msg;BizCodeEnum(int code, String msg){this.code = code;this.msg = msg;}public int getCode() {return code;}public String getMsg() {return msg;}
}

(4) 定义分组接口

        这边定义新增校验分组接口AddGroup、修改校验分组接口UpdateGroup、修改状态分组接口UpdateStatusGroup。

/*** 新增数据 Group** @author wen*/
public interface AddGroup {
}
/*** 更新数据 Group** @author wen*/public interface UpdateGroup {}
/*** @author wen* @createDate 2023/3/24 15:04* @description 修改状态 Group*/
public interface UpdateStatusGroup {
}

(5)实体类 BrandEntity

观察JSR303校验注解的groups属性的值就是不同分组想要校验的实体变量。 

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@Null(message = "新增品牌id必须为空",groups = {AddGroup.class})@NotNull(message = "修改品牌id不能为空",groups = {UpdateGroup.class, UpdateStatusGroup.class})@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})private String name;/*** 品牌logo地址*/@NotBlank(message = "品牌logo地址不能为空",groups = {AddGroup.class})@URL(message = "品牌logo地址必须是一个合法的URL",groups = {AddGroup.class,UpdateGroup.class})private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/@NotNull(message = "显示状态不能为空", groups = {AddGroup.class, UpdateStatusGroup.class})private Integer showStatus;/*** 检索首字母*/@NotEmpty(message = "检索首字母不能为空",groups = {AddGroup.class})@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})private String firstLetter;/*** 排序*/@NotNull(message = "排序不能为空",groups = {AddGroup.class})@Min(value = 0, message = "排序必须大于等于0的整数",groups = {AddGroup.class,UpdateGroup.class})private Integer sort;}

(6)控制层使用@Validated注解开启分组校验

@RestController
@RequestMapping("product/brand")
public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存*/@RequestMapping("/save")public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();}/*** 修改*/@RequestMapping("/update")public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){brandService.updateDetail(brand);return R.ok();}/*** 修改状态*/@RequestMapping("/update/status")public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){brandService.updateById(brand);//brandService.updateBrand(brand);return R.ok();}

(7)测试

 2. 自定义校验

(1)引入依赖 pom.xml

        在基础校验和分组校验的基础上添加validation-api依赖,该依赖中包含自定义校验所需的校验器等。

<!-- 数据校验 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency><!--   自定义JSR303校验     -->
<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version>
</dependency>

(2)和(3)同分组校验的(2)和(3)

(4)自定义校验注解(默认消息提示)、自定义校验器

 1)编写一个自定义的校验注解默认message定义在src/main/resources/ValidationMessages.properties文件中2)编写一个自定义的校验器3)关联自定义的校验注解和自定义的校验器,使用@Constraint注解

注意:一个自定义注解可以指定多个不同的校验器,适配不同的校验。

/*** @author wen* @createDate 2023/3/24 14:17* @description 自定义校验注解*/
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class}
) // 指定校验器
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {String message() default "{com.wen.common.validator.anno.ListValue.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};int[] vals() default {};
}

ValidationMessages.properties

com.wen.common.validator.anno.ListValue.message=必须提交指定的值
/*** @author WangWenwen* @createDate 2023/3/24 14:20* @description 自定义ListValue校验器* ConstraintValidator<A,T>:A-指定注解;T-指定什么类型的数据**/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {private Set<Integer> set = new HashSet<>();@Overridepublic void initialize(ListValue constraintAnnotation) {int[] vals = constraintAnnotation.vals();if(vals!=null && vals.length>0){for (int val : vals) {set.add(val);}}}/**** @param integer 需要校验的值* @param constraintValidatorContext* @return*/@Overridepublic boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {return set.contains(integer);}
}

(5)实体类 BrandEntity

        实体类中的showStatus变量使用@ListValue注解。

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@Null(message = "新增品牌id必须为空",groups = {AddGroup.class})@NotNull(message = "修改品牌id不能为空",groups = {UpdateGroup.class, UpdateStatusGroup.class})@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})private String name;/*** 品牌logo地址*/@NotBlank(message = "品牌logo地址不能为空",groups = {AddGroup.class})@URL(message = "品牌logo地址必须是一个合法的URL",groups = {AddGroup.class,UpdateGroup.class})private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/@NotNull(message = "显示状态不能为空", groups = {AddGroup.class, UpdateStatusGroup.class})@ListValue(vals = {0,1}, groups = {AddGroup.class})private Integer showStatus;/*** 检索首字母*/@NotEmpty(message = "检索首字母不能为空",groups = {AddGroup.class})@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})private String firstLetter;/*** 排序*/@NotNull(message = "排序不能为空",groups = {AddGroup.class})@Min(value = 0, message = "排序必须大于等于0的整数",groups = {AddGroup.class,UpdateGroup.class})private Integer sort;}

(6)控制层在分组的基础上

@ListValue指定的分组是新增。

@RestController
@RequestMapping("product/brand")
public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存*/@RequestMapping("/save")public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();}
}

(7)测试 

这篇关于数据校验 JSR303校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/299931

相关文章

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

mysql中的数据目录用法及说明

《mysql中的数据目录用法及说明》:本文主要介绍mysql中的数据目录用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、版本3、数据目录4、总结1、背景安装mysql之后,在安装目录下会有一个data目录,我们创建的数据库、创建的表、插入的

Navicat数据表的数据添加,删除及使用sql完成数据的添加过程

《Navicat数据表的数据添加,删除及使用sql完成数据的添加过程》:本文主要介绍Navicat数据表的数据添加,删除及使用sql完成数据的添加过程,具有很好的参考价值,希望对大家有所帮助,如有... 目录Navicat数据表数据添加,删除及使用sql完成数据添加选中操作的表则出现如下界面,查看左下角从左

SpringBoot中4种数据水平分片策略

《SpringBoot中4种数据水平分片策略》数据水平分片作为一种水平扩展策略,通过将数据分散到多个物理节点上,有效解决了存储容量和性能瓶颈问题,下面小编就来和大家分享4种数据分片策略吧... 目录一、前言二、哈希分片2.1 原理2.2 SpringBoot实现2.3 优缺点分析2.4 适用场景三、范围分片

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

Oracle 数据库数据操作如何精通 INSERT, UPDATE, DELETE

《Oracle数据库数据操作如何精通INSERT,UPDATE,DELETE》在Oracle数据库中,对表内数据进行增加、修改和删除操作是通过数据操作语言来完成的,下面给大家介绍Oracle数... 目录思维导图一、插入数据 (INSERT)1.1 插入单行数据,指定所有列的值语法:1.2 插入单行数据,指

SQL Server修改数据库名及物理数据文件名操作步骤

《SQLServer修改数据库名及物理数据文件名操作步骤》在SQLServer中重命名数据库是一个常见的操作,但需要确保用户具有足够的权限来执行此操作,:本文主要介绍SQLServer修改数据... 目录一、背景介绍二、操作步骤2.1 设置为单用户模式(断开连接)2.2 修改数据库名称2.3 查找逻辑文件名

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal