新增和修改共用 DTO,校验规则不同怎么办?

新增和修改接口共用一个 DTO,校验规则却不一样。新增时 ID 不传,修改时 ID 必填;密码注册时必填,更新时可选。很多人为此写了两套 DTO 或者一堆 if-else,代码又臭又长。其实 @Validated 的分组校验天生就是解决这个问题的,一个 DTO 搞定多种场景。


一、为什么需要分组

1. 同 DTO 不同规则

一个用户 DTO,新增时不需要 id,修改时 id 必填。用 @NotNull 没法区分场景,注解要么全生效要么全不生效

传统解法是拆成 UserCreateDTO 和 UserUpdateDTO 两个类,字段大量重复,改一处得改两处,维护成本翻倍。

2. 分组校验的思路

JSR-303 规范支持把校验注解归到不同组里,校验时指定激活哪个组,只有该组的注解才生效。同一个字段在不同组里可以配不同规则,互不干扰。

⚠️ 注意:分组校验需要用 @Validated 而非 @Valid,@Valid 不支持指定分组,只能全量校验。


二、定义分组

1. 空接口做标记

分组就是一个空的标记接口,不需要任何方法,纯粹用来区分场景。

public interface Create {} // 新增分组
public interface Update {} // 修改分组

命名按业务场景来,比如 Create、Update、Delete,简单明了。

2. 注解上指定分组

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。


三、接口里使用

1. 指定校验分组

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);
    }
}

2. 校验失败的响应

校验不通过会抛
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),两种用法场景不同。


四、进阶技巧

1. 分组继承

有些校验规则是公共的,每个组都要执行。可以定义一个公共组让其他组继承。

public interface Base {}                    // 公共校验组
public interface Create extends Base {}    // 继承Base,校验时Base的规则也会执行
public interface Update extends Base {}
@NotBlank(groups = Base.class) // 公共规则,Create和Update都会校验
private String username;

2. 组序列

默认分组校验是无序的,想按顺序校验,前面失败就不校验后面,用组序列。

@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);
}

3. 常见坑

⚠️ 注意:嵌套对象的校验用 @Valid 而非 @Validated,@Valid 标在字段上触发级联校验,内层对象的分组由外层 @Validated 决定。


小结

分组校验用一个 DTO 搞定多场景校验,定义空接口做分组标记,注解上 groups 指定归属,接口参数 @Validated(Group.class) 激活对应分组。公共规则用分组继承复用,有序校验用 @GroupSequence 控制顺序。

核心就一句话:同一个 DTO 不同场景不同规则,分组校验是最干净的解法,别再写两套 DTO 或一堆 if-else 了。

展开阅读全文

更新时间:2026-07-16

标签:科技   规则   注解   场景   接口   字段   参数   方法   解法   标记   定义

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight All Rights Reserved.
Powered By 61893.com 闽ICP备11008920号
闽公网安备35020302035593号

Top