分组校验(Group Validation)允许在不同的场景下对同一个实体类应用不同的校验规则。例如,在新增数据和更新数据时,可能需要对某些字段的校验规则进行调整。以下是分组校验的具体实现步骤:
一、定义分组接口
创建空的标记接口(仅用于分组标识):
// 新增时的校验分组
public interface CreateGroup {}
// 更新时的校验分组
public interface UpdateGroup {}
二、在实体类中指定分组
在字段的校验注解中,通过 groups
属性指定所属分组:
public class User {
@NotBlank(message = "用户ID不能为空", groups = UpdateGroup.class)
private String id;
@NotBlank(message = "用户名不能为空", groups = CreateGroup.class)
private String username;
@Size(min = 6, max = 20, message = "密码长度需在6到20位之间", groups = {CreateGroup.class, UpdateGroup.class})
private String password;
// 省略Getter和Setter
}
id
字段:仅在UpdateGroup
分组下校验(更新时必须校验)。username
字段:仅在CreateGroup
分组下校验(新增时必须校验)。password
字段:在CreateGroup
和UpdateGroup
分组下均校验(新增和更新时都校验)。
三、在Controller中指定校验分组
在Controller方法参数上使用 @Validated
注解(注意是 Spring 的注解,而非 @Valid
),并指定分组:
@RestController
public class UserController {
// 新增用户(校验 CreateGroup 分组)
@PostMapping("/users")
public String createUser(@Validated(CreateGroup.class) @RequestBody User user) {
return "用户新增成功";
}
// 更新用户(校验 UpdateGroup 分组)
@PutMapping("/users/{id}")
public String updateUser(@Validated(UpdateGroup.class) @RequestBody User user) {
return "用户更新成功";
}
}
四、全局异常处理
分组校验失败会抛出 ConstraintViolationException
,需在全局异常处理器中捕获:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public Map<String, Object> handleConstraintViolationException(ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation -> {
String field = violation.getPropertyPath().toString();
String message = violation.getMessage();
errors.put(field, message);
});
return Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", "参数校验失败",
"data", errors
);
}
}
五、测试示例
1. 新增用户(触发 CreateGroup
分组校验)
请求:
POST /users
Content-Type: application/json
{
"password": "12345"
}
响应:
{
"code": 400,
"message": "参数校验失败",
"data": {
"username": "用户名不能为空",
"password": "密码长度需在6到20位之间"
}
}
2. 更新用户(触发 UpdateGroup
分组校验)
请求:
PUT /users/123
Content-Type: application/json
{
"password": "12345"
}
响应:
{
"code": 400,
"message": "参数校验失败",
"data": {
"id": "用户ID不能为空",
"password": "密码长度需在6到20位之间"
}
}
六、分组校验的高级用法
1. 多分组组合校验
可以在 @Validated
中同时指定多个分组:
@Validated({CreateGroup.class, AnotherGroup.class})
2. 默认分组
如果字段未指定 groups
属性,则默认属于 Default
分组。可以通过 @Validated
的 value
属性同时包含默认分组:
@Validated(value = {UpdateGroup.class, Default.class})
3. 继承分组
分组接口可以继承其他接口,形成层级关系:
public interface AdvancedGroup extends CreateGroup {}
七、分组校验 vs 多个DTO
方案 | 优点 | 缺点 |
---|---|---|
分组校验 | 避免创建多个相似DTO,减少冗余代码 | 实体类可能包含不同场景的注解 |
多个DTO | 职责单一,结构清晰 | 需要维护多个DTO类 |
总结
通过分组校验,可以灵活控制不同场景下的校验规则,避免为每个场景创建单独的DTO类。关键步骤:
- 定义分组接口。
- 在实体类字段的校验注解中指定
groups
。 - 在Controller方法参数上使用
@Validated(分组.class)
。 - 全局捕获
ConstraintViolationException
并返回自定义错误。