Spring boot and Hibernate validate verify data

Spring boot combined with hibernate validate verification data learning

Hibernate validate data verification dependency has been introduced into the web Start dependency of spring boot. As long as the web Start dependency of the project depends, there is no need to introduce dependency.

Dependency introduction

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.4.Final</version>
</dependency>

Introduction to common notes

validate implements the specified verification functions one by one by adding annotations to the fields. Common annotations are:

annotationdescribe
@NotNullCannot be empty
@NotBlankThe string cannot be empty or empty
@NullThe verified object can only be null
@Size(min=2,max=6)The length of the verified string can only be between 2 and 6
@Max(value=7)The maximum verified parameter can only be 7
@AssertTrueThe attribute value of the label must be true
@AssertFalseThe attribute value of a dimension must be false
@PastTime must be in the past
@FutureFuture time
@PatternUsed to specify a regular expression
@NotEmptyString content is not empty
@Emailmailbox
@RangeUsed to specify numbers. Note that the range of numbers has two values, min and max
@DigitsContent must be numeric
@PositiveOrZero0 or integer

Simple use

Using validate to validate data, you can define validation annotations in object fields or method parameters

Define validation annotations in pojo
public class Entity {

    @NotBlank(message = "User name cannot be empty")
    private String username;

    @NotBlank(message = "Password cannot be empty")
    @Length(min = 6,message="Length must be greater than or equal to 6")  //Password length must be greater than six
    private String password;

    @Email(message="Illegal email address")
    private String email;

    @NotBlank(message = "Mobile phone number cannot be empty")
    @Length(min = 11, max = 11, message = "Mobile phone number can only be 11 digits")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "Wrong mobile phone number format")
    private String phone;

}

Define validation annotations on method parameters

Note that the @ Validated annotation needs to be added to the class name to use the validation annotation on the method parameters

@RestController
@RequestMapping("/demo")
@Validated //Parameter verification
public class DemoController {

    @PostMapping("/search/{page}/{size}")
    public Entity demo(@RequestBody @Validated Entity entity,
                       @PathVariable("page") @Min(value = 0)  Integer page,
                       @PathVariable("size") @Min(value = 0) Integer size){
        System.out.println(entity.getUsername()+"---"+entity.getPassword());
        System.out.println(page+"----------"+size);
        return entity;

    }
}

Unified exception handling

If it is defined on the object field, if it is verified that the data is invalid, the following will be thrown:

MethodArgumentNotValidException extends Exception  //Invalid method parameter exception

If it is defined in the method parameter, if the data is invalid, throw:

ConstraintViolationException extends ValidationException  //Constraint exception

We can define a global exception handler in spring boot to intercept the prompt information returned after an exception occurs in the verification

/**
 * Global exception handler
 */
@ControllerAdvice
@ResponseBody
public class ExceptionGlobalHandler {


    /**
     * Data verification exception handling and return -- pojo verification
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e){
        //get prompting information
        Map<String, String> map = e.getBindingResult().getFieldErrors()
                .stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        return Result.fail(ErrorCode.PARAM_ERROR,map);  //Return error prompt
    }

    /**
     * Data verification exception handling and return --- method parameter verification
     * @param e
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result constraintViolationExceptionExceptionHandle(ConstraintViolationException e){
        Map<Path, String> map = e.getConstraintViolations().stream().collect(Collectors.toMap(ConstraintViolation::getPropertyPath, ConstraintViolation::getMessage));
        return Result.fail(ErrorCode.PARAM_ERROR,map);  //Return error prompt
    }

    /**
     * The requested resource does not have exception error handling
     * @param e
     * @return
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public Result httpRequestMethodNotSupportedExceptionHandle(HttpRequestMethodNotSupportedException e){
        String message = e.getMessage();
        return Result.fail(ErrorCode.ACCESS_ERROR);
    }

    @ExceptionHandler(value = Exception.class)
    public Result exceptionHandle(Exception e){
        //System.out.println(e);
        String message = e.getMessage();
        return Result.fail(ErrorCode.OTHER_ERROR,message);
    }
}

Here, I encapsulate the returned result object.

0. Encapsulate the returned status code

/**
 * Return code
 */
public class StatusCode {

    public static final int OK=20000;//success
    public static final int ERROR =20001;//fail
    public static final int LOGINERROR =20002;//Wrong user name or password
    public static final int ACCESSERROR =20003;//Insufficient permissions
    public static final int REMOTEERROR =20004;//Remote call failed
    public static final int REPERROR =20005;//Repeat operation

}

1. Define an exception information enumeration class and success information return enumeration class to list various errors and error prompt information

public enum  ErrorCode {
    PARAM_ERROR(StatusCode.ERROR,"parameter is incorrect"),
    ACCESS_ERROR(StatusCode.ACCESSERROR,"The access resource does not exist"),
    LOGIN_ERROR(StatusCode.LOGINERROR,"User not logged in"),
    REPERROR_ERROR(StatusCode.REPERROR,"Please do not repeat the operation"),
    REMOTEERROR_ERROR(StatusCode.REMOTEERROR,"Remote call failed"),
    EXISTS_ERROR(StatusCode.ERROR,"Resource already exists"),
    OTHER_ERROR(StatusCode.ERROR,"Other errors");

    ErrorCode(Integer code, String mgs) {
        this.code = code;
        this.mgs = mgs;
    }
    private Integer code;
    private String mgs;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMgs() {
        return mgs;
    }
    public void setMgs(String mgs) {
        this.mgs = mgs;
    }
}

public enum SuccessResultEnums {

    INSERT_SUCCESS("Successfully added"),
    UPDATE_SUCCESS("Modified successfully"),
    DELETE_SUCCESS("Delete succeeded"),
    SELECT_SUCCESS("query was successful");
    SuccessResultEnums(String mgs) {
        this.mgs = mgs;
    }
    private Integer code = StatusCode.OK;
    private String mgs;

    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMgs() {
        return mgs;
    }
    public void setMgs(String mgs) {
        this.mgs = mgs;
    }
}

2. Create a static method in the Result return object, and the user can quickly build the return object

package com.bishe.common.pojo;

import com.bishe.common.enums.ErrorResultEnums;
import com.bishe.common.enums.SuccessResultEnums;
import com.fasterxml.jackson.annotation.JsonInclude;

/**
 * Return result entity class
 */
@JsonInclude(JsonInclude.Include.NON_NULL) //If parameter is empty, serialization is not converted to a string
public class Result<T> {

    private boolean flag;//Is it successful
    private Integer code;//Return code
    private String message;//Return message

    private T data;//Return data

    /**
     * Custom parameter construction (including dataset)
     * @param flag
     * @param code
     * @param message
     * @param data
     */
    public Result(boolean flag, Integer code, String message, Object data) {
        this.flag = flag;
        this.code = code;
        this.message = message;
        this.data = (T)data;
    }
    /**
     * Custom parameter construction (excluding dataset)
     * @param flag
     * @param code
     * @param message
     */
    public Result(boolean flag, Integer code, String message) {
        this.flag = flag;
        this.code = code;
        this.message = message;
    }

    public Result(boolean flag, Integer code) {
        this.flag = flag;
        this.code = code;
    }

    public Result() {
        this.flag = true;
        this.code = StatusCode.OK;
        this.message = "Successful execution";
    }

    /**
     * The operation is successful, including the dataset (custom return information)
     * @return
     */
    public static Result ok(String message,Object data){
        return new Result(true,StatusCode.OK,message,data);
    }

    /**
     * The operation is successful, excluding the dataset (custom return information)
     * @param message
     * @return
     */
    public static Result ok(String message){
        return new Result(true,StatusCode.OK,message);
    }


    /**
     * The operation was successful, including the dataset
     * @return
     */
    public static Result ok(SuccessResultEnums successResultEnums,Object data){
        return new Result(true,successResultEnums.getCode(),successResultEnums.getMgs(),data);
    }
    /**
     * The operation was successful without a dataset
     * @param successResultEnums
     * @return
     */
    public static Result ok(SuccessResultEnums successResultEnums){
        return new Result(true,successResultEnums.getCode(),successResultEnums.getMgs());
    }

    /**
     * Operation exception, return containing data set
     * @return
     */
    public static Result fail(ErrorResultEnums errorCode, Object obj){
        Result result = new Result();
        result.setFlag(false); //fail
        result.setCode(errorCode.getCode()); //Set status code
        result.setMessage(errorCode.getMgs()); //Set return information
        result.setData(obj);
        return result;
    }

    /**
     * The operation is abnormal, and the returned data set does not contain
     * @return
     */
    public static Result fail(ErrorResultEnums errorCode){
        Result result = new Result();
        result.setFlag(false); //fail
        result.setCode(errorCode.getCode()); //Set status code
        result.setMessage(errorCode.getMgs()); //Set return information
        return result;
    }

    /**
     * operation failed
     * @return
     */
    public static Result fail(){
        Result result = new Result();
        result.setFlag(false); //fail
        result.setCode(StatusCode.ERROR); //Set status code
        return result;
    }
    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
        return this;
    }
}

Extension 1: Group verification

When using hibernate validate for data verification, we sometimes verify the data by situation. For example, when adding, we do not verify that the id is not empty, but when modifying, we need to verify that the id is not empty. If @ NotNull is used for verification, non null verification will also be performed during insert operation, which is not what we want. At this time, we can group the verification and use different groups for data verification under different circumstances.

Each group needs an interface as the flag of the group, but no content can be added in the interface

General grouping includes: add insert; Modify ipdate; delete; Query select

I use internal interfaces to manage these tag class interfaces

public class ValidateGroupFalg {
    /**
     * Add operation group
     */
    public static interface  InsertFlag{}
    /**
     * Delete action group
     */
    public static interface  DeleteFlag{}
    /**
     * Modify operation group
     */
    public static interface  UpdateFlag{}
    /**
     * Query operation group
     */
    public static interface  SelectFlag{}
}

For the verification user id, group verification is performed

@TableName("tb_demo")
public class TbDemo implements Serializable {

    @TableId(type = IdType.INPUT)
    @NotNull(message = "Please specify the number to modify",groups = ValidateGroupFalg.UpdateFlag.class)
    private String id;
}

When using, you only need to add verification annotation and specify grouping mark in the parameters of the receiving method

public  Result updateById(@RequestBody @Validated(ValidateGroupFalg.UpdateFlag.class) TbDemo tbDemo){
   // ....
}
Problems to be considered when using group verification

0. For fields that need to be verified in different groups, please refer to the processing method.

1. For the verification sequence, if validate is set, as long as one verification field is inconsistent, an exception will be thrown directly, and the verification of other fields will not continue. Then, we should consider the verification order of different groups and how to make the fields with low verification efficiency be verified later.

0. For fields that need to be verified in different groups, please refer to the processing method.

In validate, when grouping verification is not used, javax.validation.groups.Default is used as the Default grouping interface by Default, that is, all validate verifications that do not specify grouping belong to the Default interface group.

If a parameter needs to be verified in multiple groups, only the verification group list needs to be specified in the verification annotation; That is, use the attribute groups in the annotation.

The following is the id field that is executed when verifying the use of InsertFlag or UpdateFlag interface groups.

If the verification interface groups used in the verification annotation belong to these groups, the verification of other groups will automatically ignore these verification annotations.

@TableName("tb_demo")
public class TbDemo implements Serializable {

    @TableId(type = IdType.INPUT)
    @NotNull(message = "Please specify the number to modify",groups = {ValidateGroupFalg.UpdateFlag.class/*Modify Interface Group*/,ValidateGroupFalg.InsertFlag.class/*Add Interface Group*/})
    private String id;
}

In the controller, you need to specify the verification interface group to use. You can also use multiple verification interface groups.

private Result insert(@RequestBody
    @Validated({
        ValidateGroupFalg.InsertFlag.class,/*Use new interface group*/
        Default.class/*The verification annotation for the default group is also executed*/})
    TbDemo tbDemo){
1. For the verification sequence, if validate is set, as long as one verification field is inconsistent, an exception will be thrown directly, and the verification of other fields will not continue. Then, we should consider the verification order of different groups and how to make the fields with low verification efficiency be verified later.

Use the annotation @ GroupSequenc to add GroupSequenc above the verified pojo class.

validate will first verify the parameters of the new group, and then proceed to the next step. We only need to separate the parameters that are difficult to verify and put them at the end of the verification sequence.

@GroupSequence({
        ValidateGroupFalg.InsertFlag.class,
        ValidateGroupFalg.UpdateFlag.class,
        ValidateGroupFalg.DeleteFlag.class,
        ValidateGroupFalg.SelectFlag.class,
        Default.class})  //Define verification priority

Extension 2: user defined verification

When we need to perform some specified verifications, we can create custom verifications through the AIP provided by hibernate validator.

step

1. Create annotations and specify data processing classes

2. Create a data processing class to implement internal methods

3. Using annotations

realization

1. Create annotations and specify data processing classes

/**
 * Time string format verification annotation
 */
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateTimeValidator.class)  //Used to specify the data processing class
public @interface DateFormatValidate {
    String message() default "Time format error";

    String format() default "yyyy-MM-dd";

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

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

2. Create a data processing class to implement internal methods

import com.bishe.common.validate.DateFormatValidate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.SimpleDateFormat;

/**
 * Time string format verification
 */
public class DateTimeValidator implements ConstraintValidator<DateFormatValidate, String> {

   private DateFormatValidate dateFormatValidate ;  //Annotation reference

    
   public void initialize(DateFormatValidate constraint) {
      this.dateFormatValidate = constraint;
      //System.out.println("initialization method call...);
   }

    //The verification method returns true successfully; If it fails, false will be returned. Validate will judge and false will throw an exception
   public boolean isValid(String value, ConstraintValidatorContext context) {
      System.out.println("Start verification...");
      if (value == null) {
         return true;
      }
      String format = dateFormatValidate.format();  //Get custom time format

      if (value.length() != format.length()) {  //If the length of 2020-10-10 is different from yyyy MM DD, false is returned
         return false;
      }

       try {
     	 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); //Create time format object
         simpleDateFormat.parse(value);  //An attempt is made to convert the verified parameters by format. A success is returned as true. A failure is returned as false
      } catch (Exception e){
         return false;  //Conversion failed
      }
      return true; //Verification successful
   }
}

3. Using annotations

@DateFormatValidate(format = "yyyy/MM/dd", message = "Format error, correct format is: yyyy/MM/dd")
    private Date birthday;

Keywords: Java Hibernate Spring Boot

Added by tsilenzio on Sat, 23 Oct 2021 10:09:48 +0300