如何使用JSR 303 进行后台数据校验?

简介: 这篇文章详细介绍了如何使用JSR 303进行后端数据校验,包括JSR 303的基本概念、使用原因、常见操作,以及如何通过注解进行数据校验、分组校验和自定义校验注解的方法和实际应用示例。

文章目录

  • 一、JSR 303
    • 1、什么是 JSR 303?
    • 2、为什么使用 JSR 303?
    • 3、JSR 303 常见操作?
    • 二、使用 JSR 303 相关注解处理逻辑
    • 1、JSR 303 注解处理逻辑
      • 1.1 使用步骤
      • 1.2 实际应用
    • 2 、JSR 303 分组校验
      • 2.1 为什么使用 分组校验?
      • 2.2 使用步骤
      • 2.3 实际应用
    • 3、JSR 303 自定义校验注解
      • 3.1 为什么使用自定义校验注解?
      • 3.2 使用步骤
      • 3.3 实际应用

一、JSR 303

1、什么是 JSR 303?

  • JSR 是 Java Specification Requests 的缩写,即 Java 规范提案。
  • 存在各种各样的 JSR,简单的理解为 JSR 是一种 Java 标准。
  • JSR 303 就是数据检验的一个标准(Bean Validation (JSR 303))。
参考:https://www.jianshu.com/p/554533f88370

2、为什么使用 JSR 303?

  • 处理一段业务逻辑,首先要确保数据输入的正确性,所以需要先对数据进行检查,保证数据在语义上的正确性,再根据数据进行下一步的处理。
  • 前端可以通过 js 程序校验数据是否合法,后端同样也需要进行校验。而后端最简单的实现就是直接在业务方法中对数据进行处理,但是不同的业务方法可能会出现同样的校验操作,这样就出现了数据的冗余。为了解决这个情况,JSR 303 出现了。
  • JSR 303 使用 Bean Validation,即在 Bean 上添加相应的注解,去实现数据校验。这样在执行业务方法前,都会根据注解对数据进行校验,从而减少自定义的校验逻辑,减少代码冗余。

3、JSR 303 常见操作?

(1)可以通过简单的注解校验 Bean 属性,比如 @NotNull、@Null 等。
(2)可以通过 Group 分组自定义需要执行校验的属性。
(3)可以自定义注解并指定校验规则。
(4)支持基于 JSR 303 的实现,比如 Hibernate Validator(额外添加一些注解)。

二、使用 JSR 303 相关注解处理逻辑

1、JSR 303 注解处理逻辑

1.1 使用步骤

  • 1、在相关的 Bean 上标注需要处理的注解,并指定需要提示的信息(若不指定,会从默认配置文件中读取默认的信息)。

  • 2、在相关的方法上,使用 @Valid 注解(或者 @Validated 指定组名)标记需要被校验的数据,否则会不生效。

注意:检测到数据异常后,系统会向外抛出异常,如果做了统一异常处理,可以根据 postman 测试的结果,找到控制台打印出的相应的异常,并处理。

  • 3、处理异常。使用 BindingResult 可以获取到检测结果,然后进行处理。也可以使用 全局统一异常 处理(@RestControllerAdvice@ExceptionHandler),处理检测结果

1.2 实际应用

1、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示


import javax.validation.constraints.*;

/**
 * 品牌
 * 
 * @author zhengyuzhu
 * @email 2977429967@qq.com
 * @date 2023-02-25 23:11:31
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 品牌id
     */
    @NotNull(message = "修改必须指定品牌id")
    @TableId
    private Long brandId;
    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名必须提交")
    private String name;

}

2、修改 Controller 方法,使用 @Valid 注解标记需要检测的数据、比如在常见的增加和修改方法上,这个时候需要将前台传来的数据插入数据库


@RestController
@RequestMapping("product/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;

    /**
     * 保存新添加品牌
     */
    @RequestMapping("/save")
    public R save(@Validated @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

    /**
     * 修改
     */
    @RequestMapping("/update")
    public R update(@Validated @RequestBody BrandEntity brand){
        brandService.updateDetail(brand);
        return R.ok();
    }

}

提示:这个时候如果插入的数据不符合规范,就会抛出异常、为了避免代码冗余。可以使用全局异常来进行处理

3、集中处理异常

可以使用 BindingResult 去处理捕获到的数据并进行相关处理

/**
 * 集中处理所有异常
 */
@Slf4j

@RestControllerAdvice(basePackages = "com.zyz.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

    @ExceptionHandler(value= MethodArgumentNotValidException.class)
    public R handleVaildException(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(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        log.error("错误:",throwable);
        return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
    }

}

2 、JSR 303 分组校验

2.1 为什么使用 分组校验?

如果出现多个方法,都需要校验 Bean,且校验规则不同的时候,怎么办呢?

就比如下方,修改品牌ID的时候,提交为空的时候,提示用户的应该是修改ID不能为空。新增的时候,应该是提示用户,新增ID不能为空。但是你也可以直接写ID不能为空,但总感觉缺少点啥。

分组校验就可以去解决该问题,每个分组指定不同的校验规则,不同的方法执行不同的分组,就可以得到不同的校验结果。 也就是说,定义好分组后,在校验的时候,就可以去找对应的分组进行相关的信息提示。

    /**
     * 品牌id
     */
    @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
    @Null(message = "新增不能指定id",groups = {AddGroup.class})
    @TableId
    private Long brandId;

2.2 使用步骤

  • 1、定义一个空接口,用于指定分组,内部不需要任何实现。

  • 2、指定 注解时,通过 groups 指定分组。用于指定在某个分组条件下,才去执行校验规则。

  • 3、在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。

注:使用分组校验后,Bean 注解上若不指定分组,则不会执行校验规则。

2.3 实际应用

1 创建两个新增和修改接口

public interface AddGroup {
}

public interface UpdateGroup {
}

2 指定 注解时,通过 groups 指定分组

比如,这里的品牌id定义两个分组,一个是增加时候的校验,一个是修改时候的校验,对应不同的校验规则。也可以将通用的校验规则,以分组的时候同时管理多个。

    /**
     * 品牌id
     */
    @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
    @Null(message = "新增不能指定id",groups = {AddGroup.class})
    @TableId
    private Long brandId;
    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
    private String name;

3、在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。

    /**
     * 保存新添加品牌
     */
    @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();
    }

在这里插入图片描述

3、JSR 303 自定义校验注解

3.1 为什么使用自定义校验注解?

当上面的注解满足不了业务需求时,可以自定义校验注解,自定义校验规则。

3.2 使用步骤

  • 1、需要自定义一个校验注解。可以创建一个 ValidationMessages.properties 用于保存默认的 message 信息。

  • 2、需要自定义一个校验器,即自定义校验规则。实现 ConstraintValidator 接口,并重写相关方法。

注:initialize 方法用于初始化,可以获取 自定义的属性的值。isValid
方法用于校验,可以获取到实际的值,然后与自定义的属性值进行比较。

  • 3、将校验注解 与 校验器 关联起来。@Constraint(validatedBy = {TestValidConstraintValidator.class})

3.3 实际应用

如下例,自定义一个校验规则,判断数据是否是 0,1。当传来的数据不是这两种之一,校验不通过。

  • 1、自定义一个校验注解
/**
 * @author zyz
 */
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {

    /**
     * 添加校验
     * @return
     */
    String message() default "{com.zyz.common.valid.ListValue.message}";

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

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

    int[] vals() default { };
}

创建一个文件ValidationMessages.properties。当不符合自定义规则校验的时候,message可以获取到对应的信息

配置文件内容:com.zyz.common.valid.ListValue.message=必须提交指定的值啊

在这里插入图片描述

  • 2 自定义一个校验器TestValidConstraintValidator, 用于检测值是否合法。
/**
 * @author zyz
 */
 /**
 * 实现 ConstraintValidator 接口,
 * 其中 ConstraintValidator 的泛型,一个需要指定自定义的注解,一个需要指定需要获取的值的类型。
 * 比如:
 *  ConstraintValidator<ListValue , String> 中
 *      ListValue   表示自定义注解
 *      String      表示获取的值的类型
 * 即定义规则,判断一个 String 的值的长度是否满足条件
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    /**
     * 初始化方法
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }

    }

    //判断是否校验成功
    /**
     *
     * @param value 需要校验的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
  • 3、使用注解

使用自定义注解,这里的@ListValue 就是使用的自定义注解,当前端传来状态的值不是对应数据。就会走自定义校验判断、提示对应的信息。这里也使用了分组的形式。

    /**
     * 显示状态[0-不显示;1-显示]
     */
    @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
    @ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
    private Integer showStatus;

方法中使用

这里使用了分组,就会自动校验

    /**
     * 保存新添加品牌
     */
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

资料参考:JSR 303

相关文章
|
6月前
|
算法 Java API
在Spring Boot中实现接口签名验证通常涉及以下步骤
在Spring Boot中实现接口签名验证通常涉及以下步骤
532 4
|
7月前
|
JSON 算法 Java
SpringBoot 实现接口参数加密解密功能
SpringBoot 实现接口参数加密解密功能
442 0
|
7月前
|
安全 Java 容器
SpringBoot - 优雅的实现【业务校验】高级进阶
SpringBoot - 优雅的实现【业务校验】高级进阶
225 0
|
7月前
|
JSON 前端开发 Java
SpringBoot - 优雅的实现【参数校验】高级进阶
SpringBoot - 优雅的实现【参数校验】高级进阶
112 0
|
Java 数据库 Spring
SpringBoot 数据校验怎样操作,主要特点是什么
SpringBoot 数据校验怎样操作,主要特点是什么
|
XML Java API
SpringMVC进阶-校验框架
SpringMVC进阶-校验框架
72 0
|
安全 Java 程序员
SpringBoot中如何实现业务校验,这种方式才叫优雅!
SpringBoot中如何实现业务校验,这种方式才叫优雅!
199 0
|
前端开发 Java API
数据校验(前端数据校验、JSR303校验)
数据校验(前端数据校验、JSR303校验)
269 0
|
存储 JSON 前端开发
Spring 参数校验最佳实践及原理解析
背景 在参数校验框架出现前,业务逻辑代码中经常会充斥着参数校验的代码,将参数校验视为业务逻辑的一部分不失为是一种较为灵活的做法,然而对于一些通用的校验,如字符串不能为空、数值范围限制等,如果还放在业务逻辑中,则会导致业务代码出现大量的重复,为了避免这种问题,Java 社区提出了 JSR-303 规范,用于对 bean 进行校验。
559 0
Spring 参数校验最佳实践及原理解析
|
前端开发
GoFrame数据校验之校验结果 | Error接口对象
这篇总结分享:GoFrame数据校验的另外一个知识点:校验结果相关的总结分享。
179 0