Learn Spring Boot: (10) Use hibernate validation to complete data back-end validation

foreword

The verification of background data is also an important point in development. It is used to verify the correctness of the data, so as to prevent some illegal data from destroying the system or entering the database, causing data pollution. Since data verification may be applied to many levels, the system will Data verification requirements are more stringent and the pursuit of variability and efficiency.

To understanding

Learn a little conceptual stuff.
* JSR 303 is a standard framework provided by Java for Bean data validation, which has been included in JavaEE 6.0.
* Hibernate Validator is a reference implementation of JSR 303, so it implements several more validation rules.
* Spring 4.0 has its own independent data verification framework and supports the JSR303 standard verification framework.
* Mark a @Valid in front of the form/command object that has been marked with JSR303 annotation. After the Spring MVC framework binds the request parameters to the input object, it will call the verification framework to perform verification according to the verification rules declared by the annotation.
* Spring MVC saves the verification result by specifying the signature of the processing method: the verification result of the previous form/command object is saved to the subsequent input parameter, and the input parameter for saving the verification result must be of type BindingResult or Errors , both of which are located in the org.springframework.validation package.
* When 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

JSRProvided validation annotations:

@Null   被的注解元素必须为 null    
@NotNull    被注解的元素必须不为 null    
@AssertTrue     被注解的元素必须为 true    
@AssertFalse    被注解的元素必须为 false    
@Min(value)     被注解的元素必须是一个数字,其值必须大于等于指定的最小值    
@Max(value)     被注解的元素必须是一个数字,其值必须小于等于指定的最大值    
@DecimalMin(value)  被注解的元素必须是一个数字,其值必须大于等于指定的最小值    
@DecimalMax(value)  被注解的元素必须是一个数字,其值必须小于等于指定的最大值    
@Size(max=, min=)   被注解的元素的大小必须在指定的范围内   集合或数组 集合或数组的大小是否在指定范围内 
@Digits (integer, fraction)     被注解的元素必须是一个数字,验证是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。   
@Past   被注解的元素必须是一个过去的日期    
@Future     被注解的元素必须是一个将来的日期    
@Pattern(regex=,flag=)  被注解的元素必须符合指定的正则表达式

Hibernate ValidatorProvided validation annotations:

@NotBlank(message =)   验证字符串非null,且长度必须大于0    
@Email  被注释的元素必须是电子邮箱地址    
@Length(min=,max=)  被注解的值大小必须在指定的范围内    
@NotEmpty   被注解的字符串的必须非空    
@Range(min=,max=,message=)  验证该值必须在合适的范围内

You can use multiple verification methods on properties that need to be verified, and they take effect at the same time.
spring boot webThere hibernate-validationare dependencies, so there is no need to manually add dependencies.

use

First I wrote a few validation annotations on my entity class.

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

    //主键
    private Long id;
    //用户名
    @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
    private String username;
    //密码
    @NotBlank(message = "密码不能为空", groups = {AddGroup.class})
    private String password;
    //手机号
    @Pattern(regexp = "^1([345789])\\d{9}$",message = "手机号码格式错误")
    @NotBlank(message = "手机号码不能为空")
    private String mobile;
    //邮箱
    @Email(message = "邮箱格式不正确")
    private String email;
    //创建者
    private Long createUserId;
    //创建时间
    private Date createDate;
// ignore set and get

Validate with @Validated

First understand:
about the difference between @Valid and @Validated
* @Valid: javax.validation, is javax, which is also the standard annotation defined in jsr303
* @Validated: org.springframework.validation.annotation, is the annotation encapsulated by spring itself. org.springframework.validation.BindExceptionAn exception is thrown when parameter validation fails .

@Validated@Validis a variant of , which expands the @Validfunction of , and supports group分组校验the writing method of , so in order to verify uniformity, 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("校验失败");
        }
        return ResponseEntity.status(OK).body("校验成功");
    }

There are a few points to note:
* When you need to verify the object, you need to add spring's verification annotation @Validated, indicating that we need spring to verify it, and the verification information will be stored in the subsequent BindingResult.
* BindingResult must be adjacent to the verification object, and no parameters can be interspersed in the middle, if there are multiple verification objects @Validated @RequestBody SysUserEntity user, BindingResult result, @Validated @RequestBody SysUserEntity user1, BindingResult result1.

I'm testing with Swagger on the front end.
I send a body and enter the wrong phone number:

{
  "createDate": "",
  "createUserId": 0,
  "email": "[email protected]",
  "id": 0,
  "mobile": "12354354",
  "password": "123",
  "username": "12312"
}

Debug the result of BindingResult in the backend, and find the result:
image
just pay attention to the errorsattribute , it is an array to check all the non-compliance with the rules.

Packet check

Sometimes, when we add and update, the verification effect is different. For example, above, when I add a User, I need to determine whether the password is empty, but I don't check it when I update it. At this time, group verification is also used.

@NotBlank(message = "密码不能为空", groups = {AddGroup.class})
private String password;

Modify the verification in the Controller.

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

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

After my test, the grouping situation is divided into:
1. When the controller does not check the grouping, it is only valid for the annotations of the entity class that are not grouped.
2. When the controller verifies and adds groups, only the annotations of the current grouping of the entity class are valid, and those without annotations are also invalid.
3. When there are two groups in the @Validated({AddGroup.class, UpdateGroup.class})verification, either one of the current two groups can be verified, the two annotations appear together at the same time, and there is no problem, and the information that fails the verification will not be repeated.

custom check

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 in a piece of information, such as sb...civilized people, for example...

Custom validation annotations
// 注解可以用在哪些地方
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
// 指定校验规则实现类
@Constraint(validatedBy = {NotHaveSBValidator.class})
public @interface NotHaveSB {
    //默认错误消息
    String message() default "不能包含字符sb";
    //分组
    Class<?>[] groups() default {};
    //负载
    Class<? extends Payload>[] payload() default {};
    //指定多个时使用
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotHaveSB[] value();
    }

}
Rule check implementation class
// 可以指定检验类型,这里选择的是 String
public class NotHaveSBValidator implements ConstraintValidator<NotHaveSB, String> {
    @Override
    public void initialize(NotHaveSB notHaveSB) {

    }

    /**
     *
     * @param s 待检验对象
     * @param constraintValidatorContext 检验上下文,可以设置检验的错误信息
     * @return false 代表检验失败
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !StringUtils.isNotBlank(s) || !s.toLowerCase().contains("sb");
    }
}

All validators need to implement the ConstraintValidator interface. Its interface is also very vivid, including an initialization event method and a method for judging whether it is legal.

test it

Now on my user class, there are no extra fields to test, so let's test the password field temporarily.

//@NotBlank(message = "密码不能为空", groups = AddGroup.class)
    @NotHaveSB
    private String password;

image

Manual verification

This is how I ultimately want to handle it.
Since the front and back ends are now developed separately, when the verification fails, a custom exception is thrown, and then these exceptions are handled uniformly, and finally the relevant error message is returned to the front end for processing.

Create a new verification tool class

public class ValidatorUtils {
    private static Validator validator;

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

    /**
     * 手动校验对象
     *
     * @param object 待校验对象
     * @param groups 待校验的组
     * @throws KCException 校验不通过,则抛出 KCException 异常
     */
    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 object to be verified. When the verification is different, it throws a custom exception and handles the exception uniformly in the background.

It can be called directly in the business, and there are groups to add groups.

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

Finally, test to see if the returned result is as expected:

image

Supplement to manual verification

Decided to use the form of annotations for coding. Originally, I wanted to use the assembly of processing method parameters for verification. After writing the discovery and @responseBodycan not be used at the same time, I found that we can still use @Validateddirect verification, throw exceptions, and catch exceptions for unified processing.

    @PostMapping()
    @ApiOperation("新增")
    public ResponseEntity insert(@Validated SysUserAddForm user) 

In the global exception handling, add the handling of bound parameter exceptions org.springframework.validation.BindException:

    /**
     * 参数检验违反约束(数据校验)
     * @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(",")));
    }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325632762&siteId=291194637
Recommended