Spring Boot数据校验注解使用

数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。

相关依赖

如果是开发普通的Java项目,则需要一下依赖

    <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.9.Final</version>
   </dependency>
   <dependency>
             <groupId>javax.el</groupId>
             <artifactId>javax.el-api</artifactId>
             <version>3.0.0</version>
     </dependency>
     <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.6</version>
     </dependency>

如果是使用Spring Boot 程序的话只需要spring-boot-starter-web 就够了,它的子依赖包含了我们所需要的东西 spring-boot-starter-web包自动依赖hibernate-validator,不用再重复引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 无需引入hibernate-validator
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.3.1.Final</version>
</dependency>
-->

注解介绍

注解名 作用对象数据类型 说明
.
@Null 任意类型 验证注解的元素是否为null
.
@NotNull 任意类型 验证注解的元素是否不为null
.
@AssertTrue Boolean, boolean 验证注解的元素是否为true
.
@AssertFalse Boolean, boolean 验证注解的元素是否为false
.
@Min(value=) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 被注释元素必须是一个数字,其值必须大于等于指定的最小值
.
@Max(value=) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 被注释元素必须是一个数字,其值必须小于等于指定的最大值
.
@DecimalMin(value=) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 被注释元素必须是一个数字,其值必须大于等于指定的最小值
.
@DecimalMax(value=) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 被注释元素必须是一个数字,其值必须小于等于指定的最大值
.
@Size(max=,min=) 字符串、Collection、Map、数组 被注释的元素的大小必须在指定的范围内(包含)
.
@Digits(integer=, fraction=) BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 被注释的元素需要满足注释要求,integer为整数位数,fraction为小数位数
.
@Past java.util.Date,java.util.Calendar;Joda Time类库的日期类型 被注释的元素必须是一个过去的日期
.
@Future java.util.Date,java.util.Calendar;Joda Time类库的日期类型 被注释的元素必须是一个将来的日期
.
@Pattern(regexp=,flag=) String,任何CharSequence的子类型 被注释的元素必须符合指定的正则表达式regexp为正则表达式,flag为标志的模式
.
@NotBlank CharSequence子类型 验证字符串非 null,且长度必须大于0
.
@Email CharSequence子类型 被注释的元素必须是电子邮箱地址,也可以通过regexp和flag指定自定义的email格式
.
@Length(min=,max=) CharSequence子类型 被注释的字符串的大小必须在指定的范围内
.
@NotEmpty CharSequence子类型、Collection、Map、数组 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
.
@Range(min=,max=) BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型 验证注解的元素值在最小值和最大值之间

关于@Max and @DecimalMax (and @Min and @DecimalMin)的区别。

关于@NotNull、@NotEmpty和@NotBlank的区别

  • @NotNull 适用于任何类型被注解的元素必须不能与NULL

  • @NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0

  • @NotBlank 只能用于String上面 不能为null,长度必须大于0

数据校验

通常,我们只需要在我们需要在实体类中如Controller中加入 @Valid 或者 @Validated 注解即可完成对我们实体类的数据校验,如:

@NotBlank(message = "用户名不能空")  
private String username;
@PostMapping("/user")  
public User addUser(@Valid User user, BindingResult bindingResult) {  
    if(bindingResult.hasErrors()){  
        System.out.println(bindingResult.getFieldError().getDefaultMessage());  
        return null;  
    }  
    return userResposity.save(user);  
} 

而@Valid 与 @Validated的用法如下:

@Valid
  • @Valid注解用于校验,所属包为:javax.validation.Valid 。
  • 需要在实体类的相应字段上添加用于充当校验条件的注解,如:@Min 等(参见上面例子)
  • 其次在controller层的方法的要校验的参数上添加@Valid注解,并且需要传入BindingResult对象,用于获取校验失败情况下的反馈信息
  • bindingResult.getFieldError.getDefaultMessage()用于获取相应字段上添加的message中的内容,如:@NotBlank注解中message属性的内容
@Validated
  • @Validated是@Valid 的一次封装,是Spring提供的校验机制使用。@Valid不提供分组功能
@Validated的特殊用法
  • 分组

    当一个实体类需要多种验证方式时,例:对于一个实体类的id来说,新增的时候是不需要的,对于更新时是必须的。

    可以通过groups对验证进行分组

    分组接口类(通过向groups分配不同类的class对象,达到分组目的):

    package com.valid.interfaces;  
    
    public interface First {  
    
    }
    

    实体类:

    package com.valid.pojo;  
    
    import javax.validation.constraints.Size;  
    import org.hibernate.validator.constraints.NotEmpty;  
    
    import com.valid.interfaces.First;  
    
    public class People {  
    
        //在First分组时,判断不能为空  
        @NotEmpty(groups={First.class})  
        private String id;  
    
        //name字段不为空,且长度在3-8之间  
        @NotEmpty  
        @Size(min=3,max=8)  
        private String name;  
    
        public String getName() {  
            return name;  
        }  
    
        public void setName(String name) {  
            this.name = name;  
        }  
    
        public String getId() {  
            return id;  
        }  
    
        public void setId(String id) {  
            this.id = id;  
        }  
    }
    

    注:

    (1)不分配groups,默认每次都要进行验证

    (2)对一个参数需要多种验证方式时,也可通过分配不同的组达到目的。例:

    @NotEmpty(groups={First.class})  
    @Size(min=3,max=8,groups={Second.class})  
    private String name; 
    

    控制类:

    package com.valid.controller;  
    
    import org.springframework.stereotype.Controller;  
    import org.springframework.validation.BindingResult;  
    import org.springframework.validation.annotation.Validated;  
    import org.springframework.web.bind.annotation.RequestMapping;  
    import org.springframework.web.bind.annotation.ResponseBody;  
    
    import com.valid.interfaces.First;  
    import com.valid.pojo.People;  
    
    @Controller  
    public class FirstController {  
    
        @RequestMapping("/addPeople")  
        //不需验证ID  
        public @ResponseBody String addPeople(@Validated People p,BindingResult result)  
        {  
            System.out.println("people's ID:" + p.getId());  
            if(result.hasErrors())  
            {  
                return "0";  
            }  
            return "1";  
        }  
    
        @RequestMapping("/updatePeople")  
        //需要验证ID  
        public @ResponseBody String updatePeople(@Validated({First.class}) People p,BindingResult result)  
        {  
            System.out.println("people's ID:" + p.getId());  
            if(result.hasErrors())  
            {  
                return "0";  
            }  
            return "1";  
        }  
    }
    

    注:

    @Validated没有添加groups属性时,所有参数的验证类型都有分组(即本例中People的name的@NotEmpty、@Size都添加groups属性),则不验证任何参数

  • 组序列

    默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要。

    例:

    (1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。

    (2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。
    一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。

    分组接口类 (通过@GroupSequence注解对组进行排序):

    package com.valid.interfaces;  
    
    public interface First {  
    
    }  
    
    package com.valid.interfaces;  
    
    public interface Second {  
    
    }  
    
    package com.valid.interfaces;  
    
    import javax.validation.GroupSequence;  
    
    @GroupSequence({First.class,Second.class})  
    public interface Group {  
    
    }
    

实体类:

  package com.valid.pojo;  

  import javax.validation.constraints.Size;  
  import org.hibernate.validator.constraints.NotEmpty;  

  import com.valid.interfaces.First;  
  import com.valid.interfaces.Second;  

  public class People {  

      //在First分组时,判断不能为空  
      @NotEmpty(groups={First.class})  
      private String id;  

      //name字段不为空,且长度在3-8之间  
      @NotEmpty(groups={First.class})  
      @Size(min=3,max=8,groups={Second.class})  
      private String name;  

      public String getName() {  
          return name;  
      }  

      public void setName(String name) {  
          this.name = name;  
      }  

      public String getId() {  
          return id;  
      }  

      public void setId(String id) {  
          this.id = id;  
      }  
  } 

控制类:

  package com.valid.controller;  

  import org.springframework.stereotype.Controller;  
  import org.springframework.validation.BindingResult;  
  import org.springframework.validation.annotation.Validated;  
  import org.springframework.web.bind.annotation.RequestMapping;  
  import org.springframework.web.bind.annotation.ResponseBody;  

  import com.valid.interfaces.Group;  
  import com.valid.pojo.People;  
  import com.valid.pojo.Person;  

  @Controller  
  public class FirstController {  

      @RequestMapping("/addPeople")  
      //不需验证ID  
      public @ResponseBody String addPeople(@Validated({Group.class}) People p,BindingResult result)  
      {  
          if(result.hasErrors())  
          {  
              return "0";  
          }  
          return "1";  
      }  
  }
  • 验证多个对象

    package com.valid.controller;  
    
    import org.springframework.stereotype.Controller;  
    import org.springframework.validation.BindingResult;  
    import org.springframework.validation.annotation.Validated;  
    import org.springframework.web.bind.annotation.RequestMapping;  
    import org.springframework.web.bind.annotation.ResponseBody;  
    
    import com.valid.pojo.People;  
    import com.valid.pojo.Person;  
    
    @Controller  
    public class FirstController {  
    
        @RequestMapping("/addPeople")  
        public @ResponseBody String addPeople(@Validated People p,BindingResult result,@Validated Person p2,BindingResult result2)  
        {  
            if(result.hasErrors())  
            {  
                return "0";  
            }  
            if(result2.hasErrors())  
            {  
                return "-1";  
            }  
            return "1";  
        }  
    }
    

异常处理

上述的方法是能校验成功,但是有个问题就是返回参数并不理想,前端也并不容易处理返回参数,所以我们添加一下全局异常处理,然后添加一下全局统一返回参数这样比较规范。

创建一个GlobalExceptionHandler类,在类上方添加@RestControllerAdvice注解然后添加以下代码:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ReturnVO handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
    log.error(e.getMessage(), e);
    return new ReturnVO().error(e.getBindingResult().getFieldError().getDefaultMessage());
}

通过@ExceptionHandler注解我们可以对验证时抛出的异常进行捕获,对异常结果进行封装处理。


一个好奇的人