Spring Boot 中的数据校验(Validation)

更新于 2025-12-30

Alejandro Ugarte 2019-02-07

1. 概述

在处理用户输入验证时,Spring Boot 开箱即用地提供了强大的支持。

尽管 Spring Boot 支持与自定义验证器无缝集成,但执行验证的事实标准是 Hibernate Validator —— Bean Validation 规范的参考实现。

在本教程中,我们将学习如何在 Spring Boot 中验证领域对象。

2. Maven 依赖

我们将通过构建一个基本的 REST 控制器来学习如何在 Spring Boot 中验证领域对象。该控制器将首先接收一个领域对象,然后使用 Hibernate Validator 对其进行验证,最后将其持久化到内存中的 H2 数据库中。

项目的依赖项相当标准:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.5.7</version>
</dependency> 
<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId>
    <version>2.3.232</version> 
    <scope>runtime</scope>
</dependency>

如上所示,我们在 pom.xml 文件中包含了 spring-boot-starter-web,因为我们创建 REST 控制器时需要它。此外,请务必在 Maven Central 上检查 spring-boot-starter-data-jpa 和 H2 数据库的最新版本。

从 Spring Boot 2.3 开始,我们还需要显式添加 spring-boot-starter-validation 依赖:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
    <version>3.5.7</version>
</dependency>

3. 一个简单的领域类

项目依赖配置完成后,接下来我们需要定义一个示例 JPA 实体类,用于建模用户。

让我们看一下这个类:

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @NotBlank(message = "姓名是必填项")
    private String name;
    
    @NotBlank(message = "邮箱是必填项")
    private String email;
    
    // 标准构造函数 / setter / getter / toString 方法
        
}

我们的 User 实体类实现非常简洁,但它清晰地展示了如何使用 Bean Validation 的约束注解来限制 nameemail 字段。

为简化起见,我们仅使用了 @NotBlank 约束,并通过 message 属性指定了错误消息。

因此,当 Spring Boot 验证该类实例时,被约束的字段必须非空,并且去除前后空白后的长度必须大于零。

此外,Bean Validation 还提供了许多其他实用的约束注解,允许我们在约束类上应用和组合不同的验证规则。更多信息请参阅 官方 Bean Validation 文档

由于我们将使用 Spring Data JPA 将用户保存到内存中的 H2 数据库,因此还需要定义一个简单的仓库接口,以提供对 User 对象的基本 CRUD 功能:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

4. 实现 REST 控制器

当然,我们需要实现一个层,用于获取分配给 User 对象约束字段的值,以便对其进行验证,并根据验证结果执行进一步操作。

Spring Boot 通过实现一个 REST 控制器,使这一看似复杂的过程变得非常简单。

让我们看看 REST 控制器的实现:

@RestController
public class UserController {

    @PostMapping("/users")
    ResponseEntity<String> addUser(@Valid @RequestBody User user) {
        // 持久化用户
        return ResponseEntity.ok("用户有效");
    }
    
    // 标准构造函数 / 其他方法
    
}

在 Spring REST 上下文中,addUser() 方法的实现相当标准。

当然,最关键的部分是使用了 @Valid 注解。

当 Spring Boot 发现一个参数被 @Valid 注解标记时,它会自动启动默认的 JSR 380(即 Bean Validation 2.0)实现——Hibernate Validator——并对该参数进行验证。

如果目标参数未能通过验证,Spring Boot 将抛出 MethodArgumentNotValidException 异常。

5. 使用 @ExceptionHandler 注解

虽然让 Spring Boot 自动验证传入 addUser() 方法的 User 对象非常方便,但这一过程还缺少一个关键环节:如何处理验证结果

@ExceptionHandler 注解允许我们通过一个方法处理指定类型的异常。

因此,我们可以用它来处理验证错误:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

我们指定了要处理的异常类型为 MethodArgumentNotValidException。因此,当传入的 User 对象无效时,Spring Boot 将调用此方法。

该方法将每个无效字段的名称及其验证后的错误消息存储在一个 Map 中,然后将该 Map 以 JSON 格式返回给客户端进行进一步处理。

简而言之,REST 控制器使我们能够轻松处理不同端点的请求、验证 User 对象,并以 JSON 格式发送响应。

这种设计足够灵活,可以通过多种 Web 层技术处理控制器响应,从 Thymeleaf 等模板引擎,到 Angular 等功能完整的 JavaScript 框架均可支持。

6. 测试 REST 控制器

我们可以使用集成测试轻松测试 REST 控制器的功能。

首先,模拟/自动装配 UserRepository 接口的实现、UserController 实例以及一个 MockMvc 对象:

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

    @MockBean
    private UserRepository userRepository;
    
    @Autowired
    UserController userController;

    @Autowired
    private MockMvc mockMvc;

    //...
    
}

由于我们只测试 Web 层,因此使用了 @WebMvcTest 注解。它使我们能够轻松使用 MockMvcRequestBuildersMockMvcResultMatchers 类提供的静态方法来测试请求和响应。

现在,我们分别使用有效和无效的 User 对象作为请求体来测试 addUser() 方法:

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
    MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
    String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content()
        .contentType(textPlainUtf8));
}

@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
    String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isBadRequest())
      .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("姓名是必填项")))
      .andExpect(MockMvcResultMatchers.content()
        .contentType(MediaType.APPLICATION_JSON_UTF8));
}

此外,我们也可以使用 Postman 等免费的 API 生命周期测试工具来测试 REST 控制器 API。

7. 运行示例应用程序

最后,我们可以通过一个标准的 main() 方法运行示例项目:

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("Bob", "bob@domain.com");
            User user2 = new User("Jenny", "jenny@domain.com");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

如预期所示,我们应该会在控制台看到两个 User 对象被打印出来。

http://localhost:8080/users 端点发送一个包含有效 User 对象的 POST 请求,将返回字符串 "用户有效"

同样地,如果发送的 User 对象缺少 nameemail 值,则会返回如下响应:

{
  "name": "姓名是必填项",
  "email": "邮箱是必填项"
}

8. 结论

在本文中,我们学习了在 Spring Boot 中执行验证的基础知识。