Jakarta Validation 校验注解速查手册

文章目录

  • 1.常见注解
    • 1.1空值与非空校验
    • 1.2数值范围校验
    • 1.3字符串与格式校验
    • 1.4.布尔值校验
    • 1.5日期与时间校验
    • 1.6重复注解(Repeatable Annotations)
  • 2.@Valid VS @Validated
    • 2.1核心区别
    • 2.2引入依赖
    • 2.3DTO示例
    • 2.4Controller示例
  • 3.校验异常及相关处理
    • 3.1三种校验异常及触发场景
    • 3.2异常处理

jakarta.validation.constraints包提供了 Jakarta Bean Validation 规范的核心内置注解,覆盖空值检查数值范围字符串格式布尔值日期时间等常见校验场景。下面按功能分类列出常用注解及其说明。

1.常见注解

1.1空值与非空校验

注解说明支持类型
@Null元素必须为null任意类型
@NotNull元素不能为null任意类型
@NotEmpty元素不能为null,且不能为空(如集合、字符串、数组等)CharSequenceCollectionMap、数组
@NotBlank元素不能为null,且必须至少包含一个非空白字符(会 trim 空白后再检查)CharSequence

@NotBlank@NotEmpty更严格,适合校验用户输入的非空字符串。

1.2数值范围校验

注解说明支持类型
@Min(value)/@Max(value)数值必须大于/等于或小于/等于指定的整数值BigIntegerbyteshortintlong及包装类
@DecimalMin(value)/@DecimalMax(value)数值必须大于/等于或小于/等于指定的十进制数值BigDecimalCharSequence、数值类型
@Digits(integer,fraction)数值的整数位数和小数位数必须在指定范围内BigDecimalBigIntegerCharSequence、数值类型
@Positive/@PositiveOrZero数值必须为正数(或正数或零)数值类型
@Negative/@NegativeOrZero数值必须为负数(或负数或零)数值类型

1.3字符串与格式校验

注解说明支持类型
@Size(min,max)元素的大小(如字符串长度、集合大小)必须在minmax之间CharSequenceCollectionMap、数组
@Pattern(regexp)字符串必须匹配指定的正则表达式CharSequence
@Email字符串必须是一个格式合法的 Email 地址CharSequence

1.4.布尔值校验

注解说明支持类型
@AssertTrue元素必须为truebooleanBoolean
@AssertFalse元素必须为falsebooleanBoolean

null值对于@AssertTrue@AssertFalse被视为有效。

1.5日期与时间校验

注解说明支持类型
@Past/@PastOrPresent日期必须在过去(或过去或现在)DateCalendarInstantLocalDateLocalDateTime
@Future/@FutureOrPresent日期必须在未来(或未来或现在)同上

1.6重复注解(Repeatable Annotations)

Jakarta Bean Validation 2.0 开始,所有内置校验注解都支持重复使用。当需要对同一个字段应用多个相同类型的约束时,可以使用其List形式,例如:

@Pattern.List({@Pattern(regexp=".*[A-Z].*",message="密码必须包含至少一个大写字母"),@Pattern(regexp=".*[a-z].*",message="密码必须包含至少一个小写字母"),@Pattern(regexp=".*\\d.*",message="密码必须包含至少一个数字")})privateStringpassword;

2.@Valid VS @Validated

  1. @ValidJakarta Bean Validation规范提供的注解(位于jakarta.validation.Valid包下),并非Spring 框架独有。它的核心作用是触发关联对象的级联校验,是处理嵌套对象校验的标配。
  2. @ValidatedSpring 框架提供的注解(位于org.springframework.validation.annotation包下),并非Jakarta Bean Validation 规范的一部分。它的核心作用是开启 Spring 对方法参数校验的支持,并提供了分组校验的能力。

2.1核心区别

对比维度@Valid(Jakarta 标准)@Validated(Spring 专属)
所属框架Jakarta Bean Validation(JSR 规范)Spring Framework
核心作用对象内部级联校验(验证嵌套对象)方法级参数校验+分组校验
使用位置方法参数、字段(用于嵌套对象)、构造器方法参数方法(需放在类上才能激活 AOP 代理)
能否指定分组❌ 不能,只走Default分组,可通过@Validated(GroupA.class)指定

2.2引入依赖

  1. Jakarta Bean Validation

    引入依赖:

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

    依赖的传递链如下:

    spring-boot-starter-validation └── hibernate-validator(校验实现) └── jakarta.validation-api(校验接口定义) └── jakarta.validation.constraints ← @NotNull、@NotEmpty 等注解所在
    • jakarta.validation-api:定义接口规范(@NotNull、@NotEmpty 等注解都在这里)。
    • hibernate-validator:上述规范的具体实现者。
    • spring-boot-starter-validation:Spring Boot 将两者打包在一起,项目中引入这个 starter 即可同时获得接口和实现。
  2. Spring框架提供的校验注解

    引入依赖:

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

    依赖的传递链如下:

    spring-boot-starter-web └── spring-webmvc └── spring-context ← @Validated 定义在此

2.3DTO示例

publicclassUserBo{@Valid//少了这个,address的内部校验不会触发privateAddressaddress;@NotBlank(message="用户名不能为空")@Size(min=2,max=20,message="用户名长度须为2-20个字符")privateStringusername;@Email(message="邮箱格式不正确")privateStringemail;@Pattern(regexp="^1[3-9]\\d{9}$",message="手机号格式不正确")privateStringphone;@Min(value=0,message="年龄不能为负数")@Max(value=150,message="年龄不能超过150岁")privateIntegerage;@Positive(message="金额必须大于0")@Digits(integer=10,fraction=2,message="金额格式不正确")privateBigDecimalamount;@Future(message="生效时间必须是未来时间")privateLocalDateTimeeffectiveTime;}publicclassAddress{@NotBlankprivateStringprovince;@NotBlankprivateStringcity;}

2.4Controller示例

场景一:对@RequestParam/@PathVariable进行校验(必用@Validated

在 Spring Controller 中,如果你需要对 URL 中的参数或路径变量做校验,必须在类上加上@Validated,否则@Min@Max等注解不会生效。

@RestController@Validated// 必须加在类上,才能激活方法参数(如 @RequestParam)的校验publicclassUserController{@GetMapping("/user")publicStringgetUser(@RequestParam@Min(1)Longid){// 当 id < 1 时,Spring 会自动抛出 ConstraintViolationExceptionreturn"user";}@DeleteMapping("/user/{id}")publicStringdeleteUser(@PathVariable@Min(1)Longid){// 当 id < 1 时,Spring 会自动抛出 ConstraintViolationExceptionreturn"user";}}

场景二:分组校验(@Validated独有的杀手锏)

@Valid无法指定校验分组,而@Validated可以。这在新增(id 为空)和更新(id 必须不为空)共用一个 DTO 时非常实用。

java

// 1. 定义分组接口publicinterfaceCreateGroup{}publicinterfaceUpdateGroup{}// 2. DTO 中使用分组publicclassUserDto{@Null(groups=CreateGroup.class)// 新增时 id 必须为空@NotNull(groups=UpdateGroup.class)// 更新时 id 必须不为空privateLongid;@NotBlank(groups={CreateGroup.class,UpdateGroup.class})privateStringname;}// 3. Controller 中使用 @Validated 指定分组@RestControllerpublicclassUserController{@PostMapping("/user")publicStringcreate(@Validated(CreateGroup.class)@RequestBodyUserDtodto){// 只校验 CreateGroup 分组下的约束(id 必须为空)return"created";}@PutMapping("/user")publicStringupdate(@Validated(UpdateGroup.class)@RequestBodyUserDtodto){// 只校验 UpdateGroup 分组下的约束(id 必须不为空)return"updated";}}

3.校验异常及相关处理

3.1三种校验异常及触发场景

异常类触发场景
MethodArgumentNotValidException@RequestBody + @Validated 参数校验失败
ConstraintViolationException方法级参数直接校验失败(非 Body)
BindException表单/Query 参数绑定校验失败
//1.MethodArgumentNotValidException@PostMapping()publicR<Void>add(@Validated(AddGroup.class)@RequestBodySecIllegalDrivingBobo){returntoAjax(secIllegalDrivingService.insertByBo(bo));}//2.ConstraintViolationException@GetMapping("/{id}")publicR<SecIllegalDrivingVo>getInfo(@NotNull(message="主键不能为空")@PathVariableLongid){returnR.ok(secIllegalDrivingService.queryById(id));}//3.BindException@GetMapping("/list")publicTableDataInfo<SecIllegalDrivingVo>list(SecIllegalDrivingBobo,PageQuerypageQuery){returnsecIllegalDrivingService.queryPageList(bo,pageQuery);}

3.2异常处理

每种异常的处理都遵循 “记录日志 + 提取 message + 统一返回” 三步模式。

  1. MethodArgumentNotValidException(Body 校验)

    @ExceptionHandler(MethodArgumentNotValidException.class)publicR<Void>handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione){//1.写入日志log.error(e.getMessage());//2.提取所有字段的 message,用 ", " 拼接Stringmessage=StreamUtils.join(e.getBindingResult().getAllErrors(),DefaultMessageSourceResolvable::getDefaultMessage,", ");//3.统一响应返回前端returnR.fail(message);}
  2. ConstraintViolationException(方法参数校验)

    @ExceptionHandler(ConstraintViolationException.class)publicR<Void>constraintViolationException(ConstraintViolationExceptione){//1.写入日志log.error(e.getMessage());//2.从 ConstraintViolation 集合中提取 getMessage(),用 ", " 拼接Stringmessage=StreamUtils.join(e.getConstraintViolations(),ConstraintViolation::getMessage,", ");//3.统一响应返回前端returnR.fail(message);}
  3. BindException(表单绑定校验)

    @ExceptionHandler(BindException.class)publicR<Void>handleBindException(BindExceptione){//1.写入日志log.error(e.getMessage());//2.获取并拼接异常信息Stringmessage=StreamUtils.join(e.getAllErrors(),DefaultMessageSourceResolvable::getDefaultMessage,", ");//3.统一响应返回前端returnR.fail(message);}