使用Hibernate validator进行数据校验

spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖

1、普通模式(默认是这个模式)

  • 普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

2、快速失败返回模式

  • 快速失败返回模式(只要有一个验证失败,则返回)

配置

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                //true 快速失败返回模式    false 普通模式
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

/**另一种快速失败配置

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .failFast( true )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

*/

常用注解

验证注解 验证的数据类型 说明
@AssertFalse Boolean,boolean 验证注解的元素值是false
@AssertTrue Boolean,boolean 验证注解的元素值是true
@NotNull 任意类型 验证注解的元素值不是null
@Null 任意类型 验证注解的元素值是null
@Min(value=值) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值大于等于@Min指定的value值
@Max(value=值) 和@Min要求一样 验证注解的元素值小于等于@Max指定的value值
@DecimalMin(value=值) 和@Min要求一样 验证注解的元素值大于等于@ DecimalMin指定的value值
@DecimalMax(value=值) 和@Min要求一样 验证注解的元素值小于等于@ DecimalMax指定的value值
@Digits(integer=整数位数, fraction=小数位数) 和@Min要求一样 验证注解的元素值的整数位数和小数位数上限
@Size(min=下限, max=上限) 字符串、Collection、Map、数组等 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Past java.util.Date,java.util.Calendar;Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间早
@Future 与@Past要求一样 验证注解的元素值(日期类型)比当前时间晚
@NotBlank CharSequence子类型 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格
@Length(min=下限, max=上限) CharSequence子类型 验证注解的元素值长度在min和max区间内
@NotEmpty CharSequence子类型、Collection、Map、数组 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Range(min=最小值, max=最大值) BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型 验证注解的元素值在最小值和最大值之间
@Email(regexp=正则表达式,flag=标志的模式) CharSequence子类型(如String) 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式
@Pattern(regexp=正则表达式,flag=标志的模式) String,任何CharSequence的子类型 验证注解的元素值与指定的正则表达式匹配
@Valid 任何非原子类型 指定递归验证关联的对象如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证

使用@Valid请求参数校验

@RequestMapping("/demo2")
public void demo2(@RequestBody @Valid DemoModel demo, BindingResult result){
   if(result.hasErrors()){
        for (ObjectError error : result.getAllErrors()) {
            System.out.println(error.getDefaultMessage());
        }
    }
}
/**注意⚠️
@Valid和BindingResult成对出现
BindingResult封装错误信息不会报错
*/

⚠️使用@Valid注解,对RequestParam对应的参数进行注解,是无效的,需要使用@Validated注解来使得验证生效。

//1.配置
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
	MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
  	/**设置validator模式为快速失败返回*/
  	postProcessor.setValidator(validator());
  	return postProcessor;
}
@Bean
public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
}

//2.方法所在的Controller上加注解@Validated
@RequestMapping("/validation")
@RestController
@Validated
public class ValidationController {
    /**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/
    @RequestMapping(value = "/demo3", method = RequestMethod.GET)
    public void demo3(@Range(min = 1, max = 9, message = "年级只能从1-9")
                      @RequestParam(name = "grade", required = true)
                      int grade,
                      @Min(value = 1, message = "班级最小只能1")
                      @Max(value = 99, message = "班级最大只能99")
                      @RequestParam(name = "classroom", required = true)
                      int classroom) {
        System.out.println(grade + "," + classroom);
    }
}
//3.验证不通过时会抛出异常,可自定义异常进行捕获
@ControllerAdvice
@Component
public class GlobalExceptionHandler {

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handle(ValidationException exception) {
        if(exception instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) exception;

            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
          /**打印验证不通过的信息*/
                System.out.println(item.getMessage());
            }
        }
        return "bad request, " ;
    }
}

model校验

@Data
public class Demo2 {
    @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
    private String length;

    /**@Size不能验证Integer,适用于String, Collection, Map and arrays*/
    @Size(min = 1, max = 3, message = "size在[1,3]之间")
    private String age;

    @Range(min = 150, max = 250, message = "range在[150,250]之间")
    private int high;

    @Size(min = 3,max = 5,message = "list的Size在[3,5]")
    private List<String> list;
}
-------------------------------------------------------
@Autowired
private Validator validator;
    
@RequestMapping("/demo3")
public void demo3(){
	
    Demo2 demo2 = new Demo2();
    demo2.setAge("111");
    demo2.setHigh(150);
    demo2.setLength("ABCDE");
    demo2.setList(new ArrayList<String>(){{add("111");add("222");add("333");}});
    Set<ConstraintViolation<Demo2>> violationSet = validator.validate(demo2);
    for (ConstraintViolation<Demo2> model : violationSet) {
        System.out.println(model.getMessage());
    }
}

/**⚠️注意
当model校验不通过时set长度不为零
可利用此来判断是否通过
*/

对象级联校验

⚠️对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证

@Data
public class Demo2 {
    @Size(min = 3,max = 5,message = "list的Size在[3,5]")
    private List<String> list;

    @NotNull
  	//⚠️内部对象添加@Valid
    @Valid
    private Demo3 demo3;
}

@Data
public class Demo3 {
    @Length(min = 5, max = 17, message = "length长度在[5,17]之间")
    private String extField;
}

分组校验

  • 分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

  • 新增用户信息的时候,不需要验证userId(因为系统生成);修改的时候需要验证userId,这时候可用用户到validator的分组验证功能。

⚠️设置validator为普通验证模式(“hibernate.validator.fail_fast”, “false”)

public interface GroupA {
}

public interface GroupB {
}
--------------------------------------
@Data
public class Person {
    @NotBlank
    @Range(min = 1,max = Integer.MAX_VALUE,message = "必须大于0",groups = {GroupA.class})
    /**用户id*/
    private Integer userId;
    @NotBlank
    @Length(min = 4,max = 20,message = "必须在[4,20]",groups = {GroupB.class})
    /**用户名*/
    private String userName;
    @NotBlank
  	//⚠️Default.class为Validated依赖中含有的接口类,非自定义接口类
  	//⚠️不配置goups,默认就是Default分组
    @Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
    /**年龄*/
    private Integer age;
    @Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {GroupB.class})
    /**性别 0:未知;1:男;2:女*/
    private Integer sex;
 
}
--------------------------------------
@RequestMapping("/demo6")
    public void demo6(@Validated({GroupA.class, GroupB.class}) Person p, BindingResult result){
        if(result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error);
            }
        }
    }

组序列

@Data
public class UserDTO {
    @NotNull(message = "id不能为空",groups = {UpdateUser.class})
    private Integer id;
    @NotBlank(message = "姓名不能为空",groups = {AddUser.class})
    private String name;
    @NotNull(message = "年龄不能为空",groups = {AddUser.class})
    private Integer age;
    @NotNull(message = "版本不能为空")//不配置goups,默认就是Default分组
    private Integer version;

 		@GroupSequence({AddUser.class, UpdateUser.class, Default.class})
    public interface AddUpdateGroup{}
    public interface AddUser{}
    public interface UpdateUser{}
}
------------------------------------------
/**
⚠️首先校验AddUser分组的注解,如果AddUser校验不通过,
就不会去校验UpdateUser和Default的分组
*/  
@PostMapping("user")
public String saveUser(@Validated(UserDTO.AddUpdateGroup.class) UserDTO userDTO){
 userMapper.addUser(userDTO);
    return "ok";
}

@Valid和@Validated 区别

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
------------------------------------------
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    Class<?>[] value() default {};
}
  • @Valid:没有分组的功能。
  • @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
  • @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
  • @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
  • 两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能