新增和修改接口共用一个 DTO,校验规则却不一样。新增时 ID 不传,修改时 ID 必填;密码注册时必填,更新时可选。很多人为此写了两套 DTO 或者一堆 if-else,代码又臭又长。其实 @Validated 的分组校验天生就是解决这个问题的,一个 DTO 搞定多种场景。
一个用户 DTO,新增时不需要 id,修改时 id 必填。用 @NotNull 没法区分场景,注解要么全生效要么全不生效。
传统解法是拆成 UserCreateDTO 和 UserUpdateDTO 两个类,字段大量重复,改一处得改两处,维护成本翻倍。
JSR-303 规范支持把校验注解归到不同组里,校验时指定激活哪个组,只有该组的注解才生效。同一个字段在不同组里可以配不同规则,互不干扰。
⚠️ 注意:分组校验需要用 @Validated 而非 @Valid,@Valid 不支持指定分组,只能全量校验。
分组就是一个空的标记接口,不需要任何方法,纯粹用来区分场景。
public interface Create {} // 新增分组
public interface Update {} // 修改分组命名按业务场景来,比如 Create、Update、Delete,简单明了。
DTO 字段的校验注解里通过 groups 属性指定属于哪个组。
@Data
public class UserDTO {
@NotNull(groups = Update.class) // 只有修改时id必填
private Long id;
@NotBlank(groups = {Create.class, Update.class}) // 新增和修改都校验
private String username;
@NotBlank(groups = Create.class) // 只有新增时密码必填
@Length(min = 6, max = 20, groups = {Create.class, Update.class})
private String password;
@Email(groups = {Create.class, Update.class}) // 两个场景都校验邮箱格式
private String email;
}⚠️ 注意:没指定 groups 的注解属于 Default 组。用 @Validated 指定分组后,Default 组的注解不会生效,除非把分组继承 Default。
Controller 方法参数上用 @Validated 指定激活哪个分组。
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping
public Result create(@Validated(Create.class) @RequestBody UserDTO dto) {
// 只校验Create分组的注解:username必填、password必填且长度6-20
userService.create(dto);
return Result.success(null);
}
@PutMapping
public Result update(@Validated(Update.class) @RequestBody UserDTO dto) {
// 只校验Update分组的注解:id必填、username必填、password选填
userService.update(dto);
return Result.success(null);
}
}校验不通过会抛
ConstraintViolationException,全局异常处理器捕获后返回友好提示。
@ExceptionHandler(ConstraintViolationException.class)
public Result handleViolation(ConstraintViolationException e) {
String msg = e.getConstraintViolations().stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.collect(Collectors.joining("; "));
return Result.fail(400, msg); // 拼接所有校验错误返回
} ⚠️ 注意:@Validated 标在方法参数上校验的是整个对象,标在类上可以校验方法参数级别的注解(如 @PathVariable、@RequestParam),两种用法场景不同。
有些校验规则是公共的,每个组都要执行。可以定义一个公共组让其他组继承。
public interface Base {} // 公共校验组
public interface Create extends Base {} // 继承Base,校验时Base的规则也会执行
public interface Update extends Base {}@NotBlank(groups = Base.class) // 公共规则,Create和Update都会校验
private String username;默认分组校验是无序的,想按顺序校验,前面失败就不校验后面,用组序列。
@GroupSequence({Create.class, Base.class, UserDTO.class})
public interface CompleteCreate {}@PostMapping
public Result create(@Validated(CompleteCreate.class) @RequestBody UserDTO dto) {
// 先校验Create组,全过了再校验Base组
userService.create(dto);
return Result.success(null);
}⚠️ 注意:嵌套对象的校验用 @Valid 而非 @Validated,@Valid 标在字段上触发级联校验,内层对象的分组由外层 @Validated 决定。
分组校验用一个 DTO 搞定多场景校验,定义空接口做分组标记,注解上 groups 指定归属,接口参数 @Validated(Group.class) 激活对应分组。公共规则用分组继承复用,有序校验用 @GroupSequence 控制顺序。
核心就一句话:同一个 DTO 不同场景不同规则,分组校验是最干净的解法,别再写两套 DTO 或一堆 if-else 了。
更新时间:2026-07-16
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight All Rights Reserved.
Powered By 61893.com 闽ICP备11008920号
闽公网安备35020302034844号