全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 6 分钟 ✍️ juanwangdev

Spring MVC 分组校验

分组校验允许在不同场景下对同一个Bean应用不同的校验规则,如创建和更新场景。

定义校验分组

Java
// 定义分组接口
public interface CreateGroup {}

public interface UpdateGroup {}

public interface DeleteGroup {}

分组校验注解

Java
@Data
public class UserDTO {

    // 更新时必须提供ID
    @NotNull(groups = UpdateGroup.class, message = "更新时ID必填")
    @Null(groups = CreateGroup.class, message = "创建时ID必须为空")
    private Long id;

    // 创建和更新时用户名都必填
    @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名不能为空")
    @Size(min = 3, max = 20, groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名3-20字符")
    private String username;

    // 创建时密码必填,更新时可选
    @NotBlank(groups = CreateGroup.class, message = "创建时密码必填")
    @Size(min = 6, max = 20, groups = CreateGroup.class, message = "密码6-20字符")
    private String password;

    // 所有场景都校验邮箱格式
    @Email(groups = {CreateGroup.class, UpdateGroup.class}, message = "邮箱格式不正确")
    private String email;

    // 删除时只校验ID
    @NotNull(groups = DeleteGroup.class, message = "删除时ID必填")
    private Long deleteId;
}

Controller中使用分组

Java
@RestController
@RequestMapping("/api/user")
public class UserController {

    @PostMapping
    public User create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) {
        // 只校验CreateGroup分组的注解
        return userService.create(dto);
    }

    @PutMapping("/{id}")
    public User update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) {
        // 只校验UpdateGroup分组的注解
        return userService.update(dto);
    }

    @DeleteMapping("/{id}")
    public void delete(@Validated(DeleteGroup.class) @RequestBody UserDTO dto) {
        // 只校验DeleteGroup分组的注解
        userService.delete(dto.getDeleteId());
    }
}

分组继承

Java
// 基础分组
public interface BaseGroup {}

// 创建分组继承基础分组
public interface CreateGroup extends BaseGroup {}

// 更新分组继承基础分组
public interface UpdateGroup extends BaseGroup {}

@Data
public class UserDTO {

    // BaseGroup的校验规则会被CreateGroup和UpdateGroup继承
    @NotBlank(groups = BaseGroup.class, message = "用户名不能为空")
    private String username;

    @NotNull(groups = CreateGroup.class, message = "创建时ID必填")
    private Long id;
}

多分组同时校验

Java
@PostMapping("/batch")
public List<User> batchCreate(
        @Validated({CreateGroup.class, BaseGroup.class}) @RequestBody List<UserDTO> dtoList) {
    // 同时校验CreateGroup和BaseGroup分组的注解
    return userService.batchCreate(dtoList);
}

Default分组

没有指定groups的注解属于Default分组:

Java
@Data
public class UserDTO {

    // 属于Default分组
    @NotBlank(message = "用户名不能为空")
    private String username;

    // 属于CreateGroup分组
    @NotNull(groups = CreateGroup.class, message = "ID必填")
    private Long id;
}

// 校验时Default分组需显式指定
@Validated({CreateGroup.class, Default.class})
public User create(@RequestBody UserDTO dto) {
    // ...
}

分组校验顺序

Java
// 定义有顺序的分组序列
@GroupSequence({FirstGroup.class, SecondGroup.class, ThirdGroup.class})
public interface OrderedGroup {}

@Data
public class UserDTO {

    @NotBlank(groups = FirstGroup.class, message = "用户名不能为空")
    private String username;

    @Size(min = 3, groups = SecondGroup.class, message = "用户名至少3字符")
    private String username;

    @Email(groups = ThirdGroup.class, message = "邮箱格式不正确")
    private String email;
}

@PostMapping
public User create(@Validated(OrderedGroup.class) @RequestBody UserDTO dto) {
    // 按顺序校验:FirstGroup → SecondGroup → ThirdGroup
    // FirstGroup校验失败则不再校验后续分组
    return userService.create(dto);
}

手动分组校验

Java
@Service
public class UserService {

    @Autowired
    private Validator validator;

    public void validateCreate(UserDTO dto) {
        Set<ConstraintViolation<UserDTO>> violations =
            validator.validate(dto, CreateGroup.class);
        handleViolations(violations);
    }

    public void validateUpdate(UserDTO dto) {
        Set<ConstraintViolation<UserDTO>> violations =
            validator.validate(dto, UpdateGroup.class);
        handleViolations(violations);
    }

    public void validateAll(UserDTO dto) {
        Set<ConstraintViolation<UserDTO>> violations =
            validator.validate(dto, CreateGroup.class, UpdateGroup.class);
        handleViolations(violations);
    }

    private void handleViolations(Set<ConstraintViolation<UserDTO>> violations) {
        if (!violations.isEmpty()) {
            List<String> messages = violations.stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
            throw new ValidationException(messages);
        }
    }
}

RESTful API分组示例

Java
@Data
public class ProductDTO {

    @NotNull(groups = {UpdateGroup.class, DeleteGroup.class}, message = "ID必填")
    private Long id;

    @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "商品名称必填")
    private String name;

    @NotNull(groups = CreateGroup.class, message = "创建时价格必填")
    @DecimalMin(value = "0.01", groups = {CreateGroup.class, UpdateGroup.class}, message = "价格必须大于0")
    private BigDecimal price;

    @NotNull(groups = {CreateGroup.class, UpdateGroup.class}, message = "库存必填")
    @Min(value = 0, groups = {CreateGroup.class, UpdateGroup.class}, message = "库存不能为负")
    private Integer stock;

    @NotNull(groups = DeleteGroup.class, message = "删除时ID必填")
    private Long deleteId;
}

@RestController
@RequestMapping("/api/product")
@Validated
public class ProductController {

    @PostMapping
    public Product create(@Validated(CreateGroup.class) @RequestBody ProductDTO dto) {
        return productService.create(dto);
    }

    @PutMapping("/{id}")
    public Product update(@Validated(UpdateGroup.class) @RequestBody ProductDTO dto) {
        return productService.update(dto);
    }

    @DeleteMapping("/{id}")
    public void delete(@Validated(DeleteGroup.class) @PathVariable Long id) {
        productService.delete(id);
    }
}

方法参数分组校验

Java
@RestController
@RequestMapping("/api")
@Validated
public class ApiController {

    @GetMapping("/search")
    public List<User> search(
            @RequestParam @NotBlank(groups = SearchGroup.class) String keyword,
            @RequestParam @Min(value = 1, groups = SearchGroup.class) Integer page) {
        return userService.search(keyword, page);
    }
}

分组校验异常处理

Java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidation(
            MethodArgumentNotValidException e) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 400);
        response.put("message", "参数校验失败");

        List<Map<String, String>> errors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> {
                Map<String, String> err = new HashMap<>();
                err.put("field", error.getField());
                err.put("message", error.getDefaultMessage());
                return err;
            })
            .collect(Collectors.toList());

        response.put("errors", errors);
        return ResponseEntity.badRequest().body(response);
    }
}

要点总结

  • 使用groups属性指定校验注解所属分组
  • @Validated(分组名.class)指定校验分组
  • 未指定groups的注解属于Default分组
  • 分组接口可继承,继承父分组的校验规则
  • @GroupSequence定义分组校验顺序
  • 可在方法参数上使用分组校验

📝 发现内容有误?点击此处直接编辑

← 上一篇 Spring MVC JSR-303 Bean Validation
下一篇 → Spring MVC 国际化消息
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库