10 分钟学会SpringValidation数据校验和全局异常处理

以下是一个使用Spring开发的简单 REST API 小程序,通过对一张user 表进行操作,代码演示如何RestAPI开发中实现数据校验、全局异常处理和返回Json格式数据。 使用的核心框架包括

  • Spring Boot
  • Spring Web
  • Spring Data JPA
  • Bean Validation(JSR-303)
  • Lombok

1. 项目依赖(pom.xml)

创建一个 Maven 项目,添加以下依赖:



    4.0.0

    com.example
    user-crud-api
    0.0.1-SNAPSHOT
    jar

    
        org.springframework.boot
        spring-boot-starter-parent
        3.4.3
         
    

    
        17
    

    
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        

        
        
            com.mysql
            mysql-connector-j
            runtime
        

        
        
            org.projectlombok
            lombok
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-validation
        

        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    


2. 数据库配置(application.properties)

配置 MySQL 数据库连接:

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA 配置
spring.jpa.hibernate.ddl-auto=update  # 根据实体自动更新数据库表结构
spring.jpa.show-sql=true  # 打印 SQL 语句(开发时使用)
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

# 服务器端口(可选)
server.port=8080

  • 注意:确保 MySQL 数据库 user_db 已创建,并调整 usernamepasswordurl 匹配您的环境。

3. 实体类(User.java)

使用 Lombok 和 Bean Validation 注解定义 User 实体:

package com.themarscloud.example.validation.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Username cannot be blank")
    @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
    private String username;

    @NotBlank(message = "Password cannot be blank")
    @Size(min = 6, message = "Password must be at least 6 characters")
    private String password;

    @NotBlank(message = "Email cannot be blank")
    @Email(message = "Email must be valid")
    private String email;
}

  • Lombok 注解@Data 自动生成 getter/setter、@Builder 提供构建器模式、@NoArgsConstructor@AllArgsConstructor 生成无参和全参构造函数。
  • Bean Validation 注解@NotBlank@Size@Email 确保数据校验。

4. Repository(UserRepository.java)

使用 Spring Data JPA 创建 UserRepository 接口:

package com.themarscloud.example.validation.repository;

import com.themarscloud.example.validation.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository {
}

  • JpaRepository 提供了默认的 CRUD 操作,无需额外实现。

5. Service 层(UserService.java)

创建服务层处理业务逻辑:

package com.themarscloud.example.validation.service;

import com.themarscloud.example.validation.model.User;
import com.themarscloud.example.validation.repository.UserRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List getAllUsers() {
        return userRepository.findAll();
    }

    public Optional getUserById(Long id) {
        return userRepository.findById(id);
    }

    public User createUser(@Valid User user) {
        return userRepository.save(user);
    }

    public User updateUser(Long id, @Valid User userDetails) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("User not found with id: " + id));

        user.setUsername(userDetails.getUsername());
        user.setPassword(userDetails.getPassword());
        user.setEmail(userDetails.getEmail());

        return userRepository.save(user);
    }

    public void deleteUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("User not found with id: " + id));
        userRepository.delete(user);
    }
}
  • @Valid:在 createUserupdateUser 方法中使用 Valid 触发 Bean Validation 校验。

6. Controller 层(UserController.java)

创建 REST 控制器处理 HTTP 请求:

  • @Valid:在接收 @RequestBody 的方法上使用 Valid 触发校验。
  • ResponseEntity:用于返回 HTTP 状态码和响应体。
package com.themarscloud.example.validation.controller;

import com.themarscloud.example.validation.model.User;
import com.themarscloud.example.validation.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private UserService userService;

    // 获取所有用户
    @GetMapping
    public ResponseEntity> getAllUsers() {
        List users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }

    // 根据 ID 获取用户
    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
                .map(ResponseEntity::ok)
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    // 创建用户
    @PostMapping
    public ResponseEntity createUser(@Valid @RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.ok(createdUser);
    }

    // 更新用户
    @PutMapping("/{id}")
    public ResponseEntity updateUser(@PathVariable Long id, @Valid @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }

    // 删除用户
    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

7. 创建全局异常处理类(GlobalExceptionHandler.java)

在 com.themarscloud.example.validation.exception 包下(如果没有该包,请创建)添加以下 @ControllerAdvice 实现:

package com.themarscloud.example.validation.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ControllerAdvice
public class GlobalExceptionHandler {

    *// 处理数据校验异常(MethodArgumentNotValidException)*
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) {
       logger.error("Runtime exception occurred: {}", ex.getMessage(), ex);
        
        Map errors = new HashMap<>();
        errors.put("timestamp", java.time.LocalDateTime.now());
        errors.put("status", HttpStatus.BAD_REQUEST.value());
        errors.put("error", "Bad Request");
        errors.put("path", ex.getBindingResult().getObjectName());

        Map fieldErrors = new HashMap<>();
        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
            fieldErrors.put(error.getField(), error.getDefaultMessage());
        }
        errors.put("errors", fieldErrors);
        
			
        
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
    *// 处理资源未找到异常(RuntimeException 或自定义异常)*
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity> handleRuntimeException(RuntimeException ex) {
        logger.error("Runtime exception occurred: {}", ex.getMessage(), ex);
        
        Map errorResponse = new HashMap<>();
        errorResponse.put("timestamp", java.time.LocalDateTime.now());
        errorResponse.put("status", HttpStatus.NOT_FOUND.value());
        errorResponse.put("error", "Not Found");
        errorResponse.put("message", ex.getMessage());

        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }
    *// 处理其他未捕获的异常*
    @ExceptionHandler(Exception.class)
    public ResponseEntity> handleGeneralException(Exception ex) {
        logger.error("Runtime exception occurred: {}", ex.getMessage(), ex);
        
        Map errorResponse = new HashMap<>();
        errorResponse.put("timestamp", java.time.LocalDateTime.now());
        errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        errorResponse.put("error", "Internal Server Error");
        errorResponse.put("message", ex.getMessage());

        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  • @ControllerAdvice:这是一个全局异常处理注解,适用于所有 @Controller 或 @RestController 的异常。
  • @ExceptionHandler:指定处理特定类型的异常(如 MethodArgumentNotValidException、 RuntimeException、 Exception)。
  • 返回格式:统一返回 JSON 格式的错误响应,包含时间戳、状态码、错误描述和具体错误信息(如果是校验错误,则包含字段级错误)。

8. 主应用程序(ExampleValidationApplication.java)

创建 Spring Boot 启动类:

package com.themarscloud.example.validation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExampleValidationApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleValidationApplication.class, args);
    }
}


9. 测试 API

使用 Postman 或 curl 测试以下端点:

  • GET /api/users:获取所有用户。

  • GET /api/users/{id}:获取指定 ID 的用户。

  • POST /api/users:创建用户(示例 JSON):

    {
      "username": "john_doe",
      "password": "password123",
      "email": "[email protected]"
    }
    
    
  • PUT /api/users/{id}:更新用户(类似 POST 的 JSON)。

  • DELETE /api/users/{id}:删除用户。

  • 数据校验示例:如果 POST 请求的 JSON 中 username 长度小于 3 或 email 格式错误,服务器会返回 400 Bad Request 响应,包含验证错误信息,例如:

    {
      "timestamp": "2025-02-25T10:00:00Z",
      "status": 400,
      "error": "Bad Request",
      "errors": [
        {
          "field": "username",
          "message": "Username must be between 3 and 50 characters"
        }
      ],
      "path": "/api/users"
    }
    

10. 测试异常处理

使用 Postman 或 curl 测试以下场景,观察返回的统一错误格式:

  • 数据校验失败: 发送以下无效 JSON 到 POST /api/users: 预期返回:

    {
      "username": "a",  *// 长度小于 3*
      "password": "123",  *// 长度小于 6*
      "email": "invalid-email"  *// 无效邮箱*
    }
    
    {
      "timestamp": "2025-02-25T12:00:00Z",
      "status": 400,
      "error": "Bad Request",
      "path": "user",
      "errors": {
        "username": "Username must be between 3 and 50 characters",
        "password": "Password must be at least 6 characters",
        "email": "Email must be valid"
      }
    }
    
  • 资源未找到: 发送 GET /api/users/999(假设 ID 999 的用户不存在): 预期返回:

    {
      "timestamp": "2025-02-25T12:00:00Z",
      "status": 404,
      "error": "Not Found",
      "message": "User not found with id: 999"
    }
    
  • 其他异常: 手动抛出 Exception(例如在服务层或控制器中添加测试代码),验证 handleGeneralException 的返回:

     {
      "timestamp": "2025-02-25T12:00:00Z",
      "status": 500,
      "error": "Internal Server Error",
      "message": "Some internal error occurred"
    }
    

11. 运行项目

  1. 确保 MySQL 服务器运行,并创建 user_db 数据库。

  2. 编译和运行项目:

    mvn clean install
    java -jar target/validation-0.0.1-SNAPSHOT.jar
    
    
  3. 访问 http://localhost:8080/api/users,使用 Postman 或浏览器测试 API。


12. 注意事项

  • Lombok 插件:在 IDE(如 IntelliJ IDEA 或 Eclipse)中安装 Lombok 插件,以支持 Lombok 注解。
  • 数据校验:确保 spring-boot-starter-validation 依赖已添加,Spring Boot 会自动集成 Hibernate Validator 进行校验。
  • 安全性:当前示例未包含认证/授权(可参考之前的 Spring Security 配置增加安全性)。
  • 日志:可添加 spring-boot-starter-loggingslf4j 进行日志记录,调试 SQL 和 API 调用。
  • 优先级:@ExceptionHandler 方法按类型匹配异常,从具体到泛型(MethodArgumentNotValidException > RuntimeException > Exception)。确保异常处理顺序合理。自定义异常:如果需要更细化的异常处理,可以创建自定义异常类(如 UserNotFoundException),并添加对应的 @ExceptionHandler。

13. 扩展建议

  • 添加 Spring Security:使用之前的 JWT 或 OAuth2 配置保护 API。
  • 分页和排序:在 UserRepository 中使用 PageableSort 实现分页和排序。
  • 单元测试:使用 spring-boot-starter-test 编写测试用例。
  • 国际化(i18n):可以对校验消息进行国际化,通过 MessageSource 自定义错误消息。
  • API 文档:使用 Swagger(Springdoc)生成 API 文档,展示异常响应。
  • 事务管理:在 UserService 中添加 @Transactional 确保数据一致性。
  • 安全增强:添加 Spring Security 保护 API,结合之前的 JWT 配置。

你可能感兴趣的:(spring,spring,boot,java)