天天看點

如何使用JSR 303 進行背景資料校驗?詳細說明+實戰練習

#頭條創作挑戰賽#

如何使用JSR 303 進行背景資料校驗?詳細說明+實戰練習

一、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 [email protected]
 * @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();
    }           
如何使用JSR 303 進行背景資料校驗?詳細說明+實戰練習

在這裡插入圖檔描述

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[1]

引用連結

[1] JSR 303: https://www.cnblogs.com/l-y-h/p/12797809.html#_label0_0

繼續閱讀