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定义分组校验顺序
- 可在方法参数上使用分组校验
📝 发现内容有误?点击此处直接编辑