一、在 Spring Boot 中,直接对 List<MyEntity>使用 @Valid验证时,集合内元素的校验可能不会自动触发。
@PostMapping("/save")
public ResponseEntity<?> saveEntities(
@RequestBody @Valid List<@Valid MyEntity> entities) {
// 业务逻辑
} MyEntity中某个参数没有传入,也没有提示,参数有加@NotNull注解
在 Spring Boot 中,直接对 List<MyEntity>使用 @Valid验证时,集合内元素的校验可能不会自动触发。以下是解决方案和步骤:
1. 使用包装类封装集合
创建一个包装类来包裹 List<MyEntity>,并在字段上添加 @Valid:
public class MyEntityListWrapper {
@Valid
private List<MyEntity> entities;
// Getter 和 Setter
}
修改 Controller 方法:
@PostMapping("/save")
public ResponseEntity<?> saveEntities(
@RequestBody @Valid MyEntityListWrapper wrapper) {
List<MyEntity> entities = wrapper.getEntities();
// 业务逻辑
}
3. 确保依赖正确
检查 pom.xml是否包含验证依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
4. 验证 MyEntity配置
确认实体类字段有 @NotNull注解:
public class MyEntity {
@NotNull(message = "字段不能为空")
private Double requiredField;
// 其他字段及 Getter/Setter
}
关键点说明:
-
集合内元素验证 :Spring 默认不支持直接校验
List<@Valid Entity>,需用包装类或手动校验。 -
错误提示:包装类方式会自动返回标准错误响应(HTTP 400 + 错误详情)。
-
测试请求 :确保 POST 请求的 Body 是 JSON 数组,且缺失了标记为
@NotNull的字段。
二、@Valid校验数据类型
@Valid可以校验数据类型 ,但需要配合 JSR 380 (Bean Validation 2.0) 的注解一起使用。@Valid本身是一个标记注解,它的作用是触发对对象属性的验证,而实际的类型校验是通过其他约束注解实现的。
数据类型校验的核心机制
1. 常用数据类型校验注解
这些注解放在实体类的字段上,配合 @Valid使用:
| 注解 | 功能描述 | 示例 |
|---|---|---|
@NotNull |
值不能为 null | @NotNull private String name; |
@NotEmpty |
字符串/集合不能为空 | @NotEmpty private List<String> items; |
@NotBlank |
字符串不能为空且必须包含非空白字符 | @NotBlank private String username; |
@Size(min, max) |
长度/大小范围限制 | @Size(min=2, max=10) private String code; |
@Min(value) |
数值最小值限制 | @Min(18) private Integer age; |
@Max(value) |
数值最大值限制 | @Max(100) private Integer score; |
@DecimalMin(value) |
小数最小值限制 | @DecimalMin("0.01") private BigDecimal price; |
@DecimalMax(value) |
小数最大值限制 | @DecimalMax("9999.99") private BigDecimal amount; |
@Digits(integer, fraction) |
整数和小数部分位数限制 | @Digits(integer=4, fraction=2) private Double value; |
@Pattern(regexp) |
正则表达式匹配 | @Pattern(regexp="[A-Za-z0-9]+") private String id; |
@Email |
邮箱格式验证 | @Email private String email; |
2. 使用示例
public class User {
@NotNull(message = "ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于等于18")
@Max(value = 100, message = "年龄必须小于等于100")
private Integer age;
@DecimalMin(value = "0.0", message = "余额不能为负数")
private BigDecimal balance;
// getters and setters
}
在 Controller 中使用 @Valid触发校验:
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody @Valid User user) {
// 如果校验失败,此方法不会执行
userService.save(user);
return ResponseEntity.ok("用户创建成功");
}
数据类型校验的工作原理
-
请求到达 :客户端发送 POST 请求到
/users -
参数绑定 :Spring 将 JSON 数据绑定到
User对象 -
校验触发 :
@Valid注解触发 Bean Validation -
约束检查:Hibernate Validator 检查所有约束注解
-
结果处理:
-
如果校验通过:执行方法体
-
如果校验失败:抛出
MethodArgumentNotValidException
-
处理校验错误
添加全局异常处理器:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
String fieldName = error.getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
}
特殊数据类型处理
1. 枚举类型校验
public class Product {
@NotNull(message = "产品类型不能为空")
private ProductType type;
}
public enum ProductType {
ELECTRONICS, CLOTHING, FOOD
}
2. 日期类型校验
public class Event {
@Future(message = "开始时间必须是未来时间")
private LocalDateTime startTime;
@Past(message = "创建时间必须是过去时间")
private LocalDateTime createdAt;
}
3. 嵌套对象校验
public class Order {
@Valid // 触发嵌套对象的校验
@NotNull(message = "客户信息不能为空")
private Customer customer;
@Valid
@NotEmpty(message = "订单项不能为空")
private List<OrderItem> items;
}
public class Customer {
@NotBlank
private String name;
@Email
private String email;
}
自定义数据类型校验
创建自定义校验注解:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhoneNumber {
String message() default "无效的手机号码";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
实现校验逻辑:
public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return PHONE_PATTERN.matcher(value).matches();
}
}
在实体类中使用:
public class Contact {
@ValidPhoneNumber
private String phone;
}
常见问题解答
Q: @Valid能校验基本类型吗?
A: 可以,但需要使用包装类型(如 Integer代替 int),因为基本类型有默认值(0),无法区分 null 和 0。
Q: 如何校验集合中的元素?
A: 使用 List<@Valid MyObject>语法或创建包装类(如前面的回答所示)。
Q: 校验失败时如何自定义响应?
A: 通过 @ExceptionHandler捕获 MethodArgumentNotValidException并构造自定义响应。
Q: 如何禁用某些校验规则?
A: 使用验证组(Validation Groups):
public interface BasicValidation {}
public interface FullValidation extends BasicValidation {}
public class User {
@NotNull(groups = BasicValidation.class)
private String username;
@Email(groups = FullValidation.class)
private String email;
}
// 在Controller中指定组
@PostMapping
public ResponseEntity<?> create(@Validated(BasicValidation.class) @RequestBody User user)
@Valid配合 Bean Validation 注解可以:
-
✅ 校验各种数据类型(字符串、数字、日期、枚举等)
-
✅ 验证值的范围、格式、长度等约束
-
✅ 处理嵌套对象和集合的校验
-
✅ 支持自定义校验规则
-
✅ 提供清晰的错误信息
通过合理使用这些功能,可以确保应用程序接收到的数据符合预期的格式和业务规则。