Learn Spring Boot: use hibernate validation to complete data back-end verification

Keywords: Database Spring Boot

preface

The verification of background data is also an important point in development. It is used to verify the correctness of data to avoid some illegal data damaging the system or entering the database, resulting in data pollution. Since data verification may be applied to many levels, the system has strict requirements for data verification and pursues variability and efficiency.

understand

Know a little conceptual stuff.
*JSR 303 is a standard framework provided by Java for Bean data validity verification, which has been included in Java EE 6.0.
*Hibernate Validator is a reference implementation of JSR 303, so it implements several more verification rules.
*Spring 4.0 has its own independent data verification framework and supports the verification framework of JSR303 standard.
*Mark @ valid in front of the form / command object annotated with JSR303 annotation. After binding the request parameter to the input parameter object, the spring MVC framework will call the verification framework to perform verification according to the verification rules declared by the annotation
*Spring MVC saves the verification result through the protocol of signing the processing method: the verification result of the previous form / command object is saved to the subsequent input parameter. The input parameter for saving the verification result must be of type BindingResult or Errors. Both classes are located in the org.springframework.validation package.
*If the Bean object to be verified and its binding result object or error object appear in pairs, other input parameters are not allowed to be declared between them
*The Errors interface provides methods to obtain error information, such as getErrorCount() or getFieldErrors(String field)
*BindingResult extends the Errors interface.

Supported annotations

Verification comments provided by JSR:

@Null   The annotated element must be null    
@NotNull    The annotated element must not be empty null    
@AssertTrue     Annotated element must be true    
@AssertFalse    Annotated element must be false    
@Min(value)     The annotated element must be a number and its value must be greater than or equal to the specified minimum value    
@Max(value)     The annotated element must be a number and its value must be less than or equal to the specified maximum value    
@DecimalMin(value)  The annotated element must be a number and its value must be greater than or equal to the specified minimum value    
@DecimalMax(value)  The annotated element must be a number and its value must be less than or equal to the specified maximum value    
@Size(max=, min=)   The size of the annotated element must be within the specified range. Is the size of the collection or array within the specified range 
@Digits (integer, fraction)     The annotated element must be a number to verify whether it conforms to the specified format, interger Specify integer precision, fraction Specify decimal precision.   
@Past   The annotated element must be a past date    
@Future     The annotated element must be a future date    
@Pattern(regex=,flag=)  The annotated element must conform to the specified regular expression

Verification comments provided by Hibernate Validator:

@NotBlank(message =)   Validation string is not null,And the length must be greater than 0    
@Email  The annotated element must be an email address    
@Length(min=,max=)  The annotated value size must be within the specified range    
@NotEmpty   The of the annotated string must be non empty    
@Range(min=,max=,message=)  Verify that the value must be within the appropriate range

You can use multiple authentication methods on the attributes that need to be verified, and they take effect at the same time.
spring boot web already has hibernate validation dependencies, so you don't need to add dependencies manually.

use

First, I wrote a few verification annotations on my entity class.

public class SysUserEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    //Primary key
    private Long id;
    //user name
    @NotBlank(message = "User name cannot be empty", groups = {AddGroup.class, UpdateGroup.class})
    private String username;
    //password
    @NotBlank(message = "Password cannot be empty", groups = {AddGroup.class})
    private String password;
    //cell-phone number
    @Pattern(regexp = "^1([345789])\\d{9}$",message = "Mobile phone number format error")
    @NotBlank(message = "Mobile phone number cannot be empty")
    private String mobile;
    //mailbox
    @Email(message = "The mailbox format is incorrect")
    private String email;
    //creator
    private Long createUserId;
    //Creation time
    private Date createDate;
// ignore set and get

Use @ Validated to verify

First understand:
About the difference between @ Valid and @ Validated
*@ Valid: javax.validation is javax, which is the specification annotation defined in jsr303
*@ Validated: org.springframework.validation.annotation is the annotation encapsulated by spring itself. Parameter verification failed. An org.springframework.validation.BindException exception is thrown.

@Validated is a variant of @ Valid, which extends the function of @ Valid and supports the writing of group verification. Therefore, in order to verify unity, try to use @ validated

Customize an interface in the controller

@PostMapping("/valid")
    public ResponseEntity<String> valid(@Validated @RequestBody SysUserEntity user, BindingResult result) {
        if (result.hasErrors()) {
            return ResponseEntity.status(BAD_REQUEST).body("Verification failed");
        }
        return ResponseEntity.status(OK).body("Verification successful");
    }

There are several points to note:
*When you need to verify an object, you need to add the spring verification annotation @ Validated, which means that we need spring to verify it, and the verification information will be stored in the following BindingResult.
*BindingResult must be close to the verification object, and no parameters can be interspersed. If there are multiple verification objects @ Validated @RequestBody SysUserEntity user, BindingResult result, @Validated @RequestBody SysUserEntity user1, BindingResult result1.

I tested it with Swagger on the front end.
I send a body and input the wrong mobile phone number:

{
  "createDate": "",
  "createUserId": 0,
  "email": "k@wuwii.com",
  "id": 0,
  "mobile": "12354354",
  "password": "123",
  "username": "12312"
}

The BindingResult result under backend debugging is found as follows:

Just pay attention to the errors attribute, which verifies all that do not comply with the rules. It is an array.

Group check

Sometimes, when we add and update, the verification effect is different. For example, in the above, I need to judge whether the password is empty when adding a User, but I don't check it when updating. Group verification is also used at this time.

@NotBlank(message = "Password cannot be empty", groups = {AddGroup.class})
private String password;

Modify the verification in the Contoller.

(@Validated({AddGroup.class}) @RequestBody SysUserEntity user, BindingResult result)

The above means that only the verification that the group is AddGroup will take effect, and the rest will be ignored.

After my test, I divided the grouping into the following:

  1. When the controller checks that there is no grouping, it is only valid for the annotation of the entity class without grouping.
  2. When the controller checks and adds groups, only the annotations of the current group of entity classes are valid, and those without annotations are invalid.
  3. When there are two groups in the verification, @ Validated({AddGroup.class, UpdateGroup.class}), it is satisfied that either of the current two groups can be verified. There is no problem that the two annotations appear together, and the information that fails the verification will not be repeated.

Custom verification

Sometimes the verification annotations provided by the system are not enough. We can customize the verification to meet our business needs.

For example: now we have a need to detect sensitive words of a piece of information, such as sb... Civilized people, for example, chestnuts

Custom verification annotation
// Where can annotations be used
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
// Specifies the validation rule implementation class
@Constraint(validatedBy = {NotHaveSBValidator.class})
public @interface NotHaveSB {
    //Default error message
    String message() default "Cannot contain characters sb";
    //grouping
    Class<?>[] groups() default {};
    //load
    Class<? extends Payload>[] payload() default {};
    //Use when specifying multiple
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotHaveSB[] value();
    }

}

Rule verification implementation class
// The inspection type can be specified. String is selected here
public class NotHaveSBValidator implements ConstraintValidator<NotHaveSB, String> {
    @Override
    public void initialize(NotHaveSB notHaveSB) {

    }

    /**
     *
     * @param s Object to be inspected
     * @param constraintValidatorContext Inspection context, you can set the error information of inspection
     * @return false Representative inspection failed
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !StringUtils.isNotBlank(s) || !s.toLowerCase().contains("sb");
    }
}

All verifiers need to implement the ConstraintValidator interface, which is also very visual, including an initialization event method and a method to judge whether it is legal or not.

Test it hello

Now there are no extra fields in my user class. Let's test the password field for the time being.

//@NotBlank(message = "password cannot be empty", groups = AddGroup.class)
    @NotHaveSB
    private String password;

Manual verification

This is the way I finally want to deal with it.
Because they are developed separately from the front end and the back end, when the verification fails, throw custom exceptions, deal with these exceptions uniformly, and finally return the relevant error prompt information to the front end for processing.

Create a new validation tool class

public class ValidatorUtils {
    private static Validator validator;

    static {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    /**
     * Manual verification object
     *
     * @param object Object to be verified
     * @param groups Group to be verified
     * @throws KCException If the verification fails, a KCException exception is thrown
     */
    public static void validateEntity(Object object, Class<?>... groups)
            throws KCException {
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            String msg = constraintViolations.parallelStream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(","));
            throw new KCException(msg);
        }
    }
}

The main thing it does is to verify our objects to be verified. When different objects pass the verification, throw custom exceptions and handle exceptions in the background.

You can call it directly in the business. You can add a group if there is a group:

@PostMapping("/valid1")
    public ResponseEntity<String> customValid(@RequestBody SysUserEntity user) {
        ValidatorUtils.validateEntity(user);
        return ResponseEntity.status(OK).body("Verification successful");
    }

Finally, test to see if the returned results meet expectations:

Supplement to manual verification

It was decided to code in the form of annotation. I originally wanted to use the assembly of processing method parameters for inspection. After writing that discovery and @ responseBody cannot be used at the same time, I found that @ Validated can still be used for direct verification, throw exceptions, and catch exceptions for unified processing.

    @PostMapping()
    @ApiOperation("newly added")
    public ResponseEntity insert(@Validated SysUserAddForm user) 

Add binding parameter exception org.springframework.validation.BindException to global exception handling:

    /**
     * Parameter verification violation constraint (data verification)
     * @param e BindException
     * @return error message
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)
    public ResponseEntity<String> handleConstraintViolationException(BindException e) {
        LOGGER.debug(e.getMessage(), e);
        return ResponseEntity.status(BAD_REQUEST).body(
                e.getBindingResult()
                        .getAllErrors()
                        .stream()
                        .map(DefaultMessageSourceResolvable::getDefaultMessage)
                        .collect(Collectors.joining(",")));
    }

Posted by jaql on Wed, 17 Nov 2021 05:11:48 -0800