Hibernate Validator服务端数据校验

1、Hibernate Validator介绍

  • Hibernate Validator是Hibernate提供的一个开源的数据校验框架,能够使用注解方式非常方便的实现服务端数据校验,免去了自己在service层编写数据校验的麻烦。
  • Hibernate Validator是 Bean Validation 的参考实现,Hibernate Validator 提供了JSR 303规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。
    官网:Hibernate Validator官网

2、使用背景

在前后端分离项目中,一般前端都会进行数据校验,但是前端的数据校验很容易被绕过,所以也要在后端使用数据校验保证数据的合法性、安全性。

3、添加依赖

  • SpringBoot2.3以前,SpringBoot的web启动器中集成了Hibernate-Validator框架的相关依赖:
    在这里插入图片描述

  • springboot-2.3 开始,校验包被独立成了一个 starter 组件,需要引入如下依赖:

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

在这里插入图片描述

4、常用注解

Constraint详细信息
@Valid被注释的元素是一个对象,需要检查此对象的所有字段值
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内
@NotBlank被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=)被注释的字符串必须是一个有效的url
@CreditCardNumber被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性

5、使用方式

  • 接口方法参数列表中添加BindResult参数封装错误结果
  • 全局异常处理,参数校验错误时不使用BindResult导致抛出异常,使用全局异常处理类统一处理

6、使用

6.1、使用@Validate注解+BindResult校验对象类参数,比如在新增及修改接口中使用

User实体类:

package com.ds.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;

/**
 * @version V1.0
 * @package com.ds.entity
 * @description: 用户实体类
 * @author: ds
 * @date: 2023/7/17
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "用户对象")
public class User {

    /**
     * 主键id 用户id
     */
    @ApiModelProperty(value = "用户ID")
    private Integer userId;

    /**
     * 用户名
     */
    @NotEmpty(message = "用户名不能为空")
    @ApiModelProperty(value = "用户名")
    private String username;

    /**
     * 密码
     */
    @NotEmpty(message = "密码不能为空")
    @Length(min=6,max = 20,message = "密码长度应在6到20之间")
    @ApiModelProperty(value = "密码")
    private String password;

    /**
     * 昵称
     */
    @ApiModelProperty(value = "用户昵称")
    private String nickname;

    /**
     * 真实姓名
     */
    @ApiModelProperty(value = "真实姓名")
    private String realname;

    /**
     * 头像
     */
    @ApiModelProperty(value = "用户头像")
    private String img;

    /**
     * 手机号
     */
    @NotEmpty(message = "手机号不能为空")
    @ApiModelProperty(value = "手机号")
    private String mobile;

    /**
     * 邮箱地址
     */
    @NotEmpty(message = "邮箱地址不能为空")
    @Email(message = "邮箱地址格式不正确")
    @ApiModelProperty(value = "邮箱地址")
    private String email;

    /**
     * 性别 M(男) or F(女)
     */
    @ApiModelProperty(value = "性别")
    private Integer gender;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    @Min(value = 12,message = "允许最小年龄为12岁")
    @Max(value = 24,message = "允许最小年龄为24岁")
    @ApiModelProperty(value = "年龄")
    private Integer age;

    /**
     * 生日
     */
    @ApiModelProperty(value = "生日")
    private Date birth;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /**
     * 更新时间
     */
    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

    /**
     * 更新时间
     */
    @NotEmpty(message = "联系人不允许为空")
    @Size(min = 1,max = 3,message = "联系人长度只允许1到3之间")
    private List<String> contacts;
}

controller类中修改接口参数列表,使用@Validate注解标记User,并使用BindResult参数接收参数校验失败内容,返回给前端错误信息。

@PutMapping
@ApiOperation(value = "修改用户接口")
public Result<String> updateUser(@RequestBody @Validated User user, BindingResult bindingResult) {
    List<ObjectError> allErrors = bindingResult.getAllErrors();
    if(!allErrors.isEmpty()){
        String validFailMsg = allErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList()).toString();
        return Result.build(validFailMsg, ResultCodeEnum.VALIDATE_FAILED);
    }
    return Result.success("");
}

6.2、使用统一异常处理方式,可以用于校验基本参数以及对象类参数

创建全局异常处理类GlobalExceptionAdvice ,接口方法参数不使用BindResult时,检验不同参数会抛出不同的异常,分别如下:

  • 校验参数为json数据时,校验失败时抛出MethodArgumentNotValidException异常
  • 校验参数为form data数据时,校验失败时抛出BindException异常
  • 校验单个或多个参数,校验失败时抛出ConstraintViolationException异常

GlobalExceptionAdvice 类:

package com.ds.common.exception.advice;

import com.ds.common.enums.ResultCodeEnum;
import com.ds.common.result.Result;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @version V1.0
 * @package com.ds.common.exception.advice
 * @description: 全局异常处理类
 * @author: ds
 * @date: 2023/7/17
 */
@RestControllerAdvice
public class GlobalExceptionAdvice {


    /**
     * 有关后端接口数据校验,不同种类的参数校验失败后对应不同的异常说明:
     *     1、使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException
     *     2、使用 form data 方式调用接口,校验异常抛出 BindException
     *     3、使用 单个参数 请求体调用接口,校验异常抛出ConstraintViolationException
     */


    /**
     * 处理 json 请求体调用接口校验失败抛出的异常
     * 使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<List<String>> MethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> validFailMsg = fieldErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return Result.build(validFailMsg, ResultCodeEnum.VALIDATE_FAILED);
    }

    /**
     * 使用form data方式调用接口,校验异常抛出 BindException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BindException.class)
    public Result<List<String>> BindException(BindException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> validFailMsg = fieldErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return Result.build(validFailMsg, ResultCodeEnum.VALIDATE_FAILED);
    }

    /**
     * 基本请求参数调用接口,校验异常抛出 ConstraintViolationException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Result<String> handler(ConstraintViolationException e) {
        StringBuffer errorMsg = new StringBuffer();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
        return Result.build(errorMsg.toString(), ResultCodeEnum.VALIDATE_FAILED);
    }
}
6.2.1、基本参数校验:controller类上一定要添加@Validate注解,否则不生效,校验失败后会抛出ConstraintViolationException异常
package com.ds.controller;

import com.ds.common.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotNull;

/**
 * @version V1.0
 * @package com.ds.controller
 * @description: 用户控制器类
 * @author: ds
 * @date: 2023/7/17 11:14
 */
@Api(tags = "用户管理模块")
@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    @GetMapping
    @ApiOperation(value = "根据用户ID查询用户信息")
    public Result<String> selectUserById(@NotNull(message = "用户ID不能为空") Integer userId) {
        return Result.success("用户张三");
    }

}

结果:

在这里插入图片描述

6.2.2、json数据校验:之前使用BindResult返回检验失败时的代码(见下面代码中注释部分)就不用写了,校验失败后会抛出MethodArgumentNotValidException异常
/**
     * 校验json数据,使用全局异常处理类处理
     *
     * @param user
     * @return
     */
    @PostMapping
    @ApiOperation(value = "新增用户接口")
    public Result<String> insertUser(@RequestBody @Validated User user) {
        //if (bindingResult.hasErrors()) {
        //    String validFailMsg = bindingResult.getFieldErrors()
        //            .stream()
        //            .map(FieldError::getDefaultMessage)
        //            .collect(Collectors.joining("|"));
        //    return ResponseEntity.ok(validFailMsg);
        //}
        return Result.success("");
    }

结果:
在这里插入图片描述

6.2.3、form data类型数据校验:校验失败后抛出BindException异常
/**
 * 对form data参数进行校验
 * 校验失败时会抛出BindException异常,全局异常处理类会进行处理并返回错误信息
 *
 * @return
 */
@PostMapping("/save")
@ApiOperation(value = "通过form data数据新增用户")
public Result<String> saveUser(@Validated User user) {
    return Result.success("用户张三");
}

结果:
在这里插入图片描述

7、分组校验

分组校验的使用场景举例:比如用户登录时可以使用用户名和密码登录,也可以使用手机号和验证码进行登录。
使用步骤:

  • 1、定义接口
  • 2、在实体类校验注解上添加 groups 属性指定分组,如:@NotEmpty(message = “密码不能为空”,groups = {LoginUsernameValid.class})
  • 3、controller接口的方法上 @Validated 注解添加分组类,如:(@Validated(LoginUsernameValid.class) LoginEntity loginEntity)

定义接口:

/**
 * @version V1.0
 * @package com.ds.validate.group
 * @description: 分组校验之用户名密码登录校验
 * @author: ds
 * @date: 2023/7/22 17:18
 */
public interface LoginUsernameValid{
}
/**
 * @version V1.0
 * @package com.ds.validate.group
 * @description: 分组校验之手机号验证码登录校验
 * @author: ds
 * @date: 2023/7/22 17:19
 */
public interface LoginMobileValid{
}

实体类LoginEntity:

package com.ds.entity;

import com.ds.validate.Mobile;
import com.ds.validate.group.LoginMobileValid;
import com.ds.validate.group.LoginUsernameValid;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotEmpty;

/**
 * @version V1.0
 * @package com.ds.entity
 * @description: 登录实体类
 * @author: ds
 * @date: 2023/7/22 20:59
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "登录实体类")
public class LoginEntity {

    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    @NotEmpty(message = "用户名不能为空",groups = {LoginUsernameValid.class})
    private String username;

    /**
     * 密码
     */
    @ApiModelProperty(value = "密码")
    @Length(min=6,max = 20,message = "密码长度应在6到20之间",groups = {LoginUsernameValid.class})
    @NotEmpty(message = "密码不能为空",groups = {LoginUsernameValid.class})
    private String password;

    /**
     * 手机号
     */
    @ApiModelProperty(value = "手机号")
    @Mobile(groups = {LoginMobileValid.class})
    @NotEmpty(message = "手机号不能为空",groups = {LoginMobileValid.class})
    private String mobile;

    /**
     * 短信验证码
     */
    @ApiModelProperty(value = "短信验证码")
    @NotEmpty(message = "短信验证码不能为空",groups = {LoginMobileValid.class})
    private String smsCode;
}

LoginController接口类:

package com.ds.controller;

import com.ds.common.result.Result;
import com.ds.entity.LoginEntity;
import com.ds.validate.group.LoginMobileValid;
import com.ds.validate.group.LoginUsernameValid;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @version V1.0
 * @package com.ds.controller
 * @description: 登录controller类
 * @author: ds
 * @date: 2023/7/22 21:07
 */
@RestController
@RequestMapping("/login")
@Api(tags = "登录管理")
public class LoginController {

    /**
     * 登录方式一:使用用户名和密码登录
     * @param loginEntity
     * @return
     */
    @ApiOperation(value = "用户名密码登录接口")
    @GetMapping("/way1")
    public Result<String> loginByUsernameAndPassword(@Validated(LoginUsernameValid.class) LoginEntity loginEntity) {
        return Result.success("登录成功");
    }

    /**
     * 登录方式二:使用手机号和验证码登录
     * @param loginEntity
     * @return
     */
    @ApiOperation(value = "手机号验证码登录接口")
    @GetMapping("/way2")
    public Result<String> loginByMobile(@Validated(LoginMobileValid.class) LoginEntity loginEntity) {
        return Result.success("登录成功");
    }
}

结果:

在这里插入图片描述

8、自定义校验

8.1 编写自定义校验注解

package com.ds.validate;

import com.ds.common.constants.GlobalConstants;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @version V1.0
 * @package com.ds.validate
 * @description: 自定义手机号校验注解
 * @author: ds
 * @date: 2023/7/22 17:01
 */
@Documented
//指定由哪个类执行校验逻辑,否则报错javax.validation.UnexpectedTypeException: HV000030: No validator
// could be found for constraint 'com.ds.validate.Mobile' validating type 'java.lang.String'.
@Constraint(validatedBy = {MobileValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface Mobile {

    String message() default GlobalConstants.MOBILE_PATTERN_FAILED;

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

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

    /**
     * @return an additional regular expression the annotated element must match. The default
     * is any string ('.*')
     */
    String regexp() default GlobalConstants.MOBILE_PATTERN;

    /**
     * @return used in combination with {@link #regexp()} in order to specify a regular
     * expression option
     */
    Pattern.Flag[] flags() default { };

    /**
     * Defines several {@code @Email} constraints on the same element.
     *
     * @see javax.validation.constraints.Email
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        javax.validation.constraints.Email[] value();
    }
}

8.2 编写执行校验逻辑类

package com.ds.validate;

import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* @version V1.0
* @package com.ds.validate
* @description: 手机号校验类
* @author: ds
* @date: 2023/7/22 17:01
*/
public class MobileValidator implements ConstraintValidator<Mobile,String> {

  private String regexp;

  @Override
  public void initialize(Mobile constraintAnnotation) {
//        ConstraintValidator.super.initialize(constraintAnnotation);
      this.regexp = constraintAnnotation.regexp();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
      if (StringUtils.isEmpty(value)) {
          //为空,不做校验
          return true;
      }
      return value.matches(regexp);
  }
}

8.3 使用

 	   /**
 	 * 手机号
 	 */
 	@Mobile
 	@NotEmpty(message = "手机号不能为空")
 	@ApiModelProperty(value = "手机号")
 	private String mobile;

结果:

在这里插入图片描述

9、源码地址

https://gitee.com/lucky-eagle/spring-boot-demos/tree/master/spring-boot-validation-demo,希望大家觉得有用的话star一下、fork一下哈哈,祝大家天天开心!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Hibernate Validator是一个用于Java Bean验证的框架,它提供了一组注解和API,用于验证Java Bean的属性值是否符合指定的规则和约束。它可以用于验证表单数据、REST API请求、数据库实体等各种场景下的数据Hibernate Validator支持多种验证规则,包括基本数据类型、字符串、日期、集合、数组等。它还支持自定义验证规则和错误消息。使用Hibernate Validator可以有效地减少代码量和提高开发效率。 ### 回答2: Hibernate Validator是一个流行的Java开源校验框架,它是基于JSR 303规范(Bean验证)的实现。它提供了一组注释和API,用于对JavaBean进行验证和校验Hibernate Validator提供了很多内置的校验注释,比如@NotNull、@NotEmpty、@Min、@Max等。这些注释可以直接应用在JavaBean的属性上,通过注释指定的校验规则来验证属性的值是否合法。同时,Hibernate Validator还支持自定义校验注释,可以根据业务需求来定义新的注释,并实现相应的校验逻辑。 Hibernate Validator不仅可以对单个属性进行校验,还支持对整个JavaBean进行校验。例如,可以在JavaBean的类级别上添加@Valid注释,来验证整个对象的合法性。此外,还可以通过分组校验来实现在不同场景下不同的校验规则。 除了注释方式外,Hibernate Validator还提供了一套强大的API,通过编程方式来进行校验和验证。通过ValidatorFactory和Validator两个主要的接口,可以创建Validator对象并执行校验操作。可以验证单个属性的值,也可以验证整个JavaBean对象。 Hibernate Validator还提供了国际化的支持,可以根据不同的区域设置显示不同的错误信息。同时,还能够将校验错误信息与具体的校验注释进行关联,方便开发者快速定位问题。 总结来说,Hibernate Validator提供了一种有效、灵活和方便的方式来对JavaBean进行校验。它的注释和API丰富多样,并且支持自定义校验规则,同时还提供了国际化和错误信息关联等特性,使得校验过程更加强大和可控。它在Java开发中的应用越来越广泛,为开发者提供了一种便捷的校验解决方案。 ### 回答3: Hibernate Validator是一个基于JSR 380规范的校验框架,它可以轻松地对Java对象进行校验。通过使用Hibernate Validator,开发人员可以在应用程序中方便地添加校验规则,并且可以验证这些规则是否被满足。 Hibernate Validator提供了一组注解,这些注解可以附加在JavaBean的属性上,以标识需要进行校验的规则。例如,@NotNull注解用于确保属性的值不为空,@Size注解用于确保字符串类型的属性的长度在指定范围内,等等。除了注解外,Hibernate Validator还提供了一些内置的校验器,用于验证各种数据类型的属性,例如字符串、数字、日期等。 使用Hibernate Validator进行校验非常简单。只需要在需要校验的JavaBean上添加注解,并在需要校验的时候调用校验方法即可。校验方法会返回一个包含校验结果的对象,开发人员可以根据需要进行处理。校验方法还可以接受一个可选的校验分组参数,用于校验不同场景下的不同规则。 Hibernate Validator还提供了一些扩展功能,用于自定义校验规则。开发人员可以创建自定义的校验注解,并编写相应的校验器来实现特定的校验逻辑。这使得Hibernate Validator非常灵活,可以满足各种不同的校验需求。 总结来说,Hibernate Validator是一个强大而灵活的校验框架,能够方便地对Java对象进行校验。使用Hibernate Validator可以增加应用程序的稳定性和可靠性,减少错误和异常的发生。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值