spring boot 数据校验validation

背景

在日常的开发中,后端经常需要都请求参数进行校验。比如注册用户时,用户名不能为空,密码长度要在6-16之间,邮箱必须符合格式等等。如果不做校验,脏数据就可能进入数据库,造成业务问题;如果校验方式不合理。代码会变得臃肿

下面将介绍一下Spring Boot 提供的 Validation(基于 JSR 303/380 规范)让我们能通过注解的方式优雅地完成参数校验,极大地提升了开发效率和代码可读性

没有使用 Validation 的传统写法

场景:创建用户接口

  • 定义接受参数对象UserDto

    // UserDTO实体类
    class UserDto {
        private String name;
        private Integer age;
        private String email;
        
        // getter和setter省略
    }
    
  • 要求:用户名不能为空,长度5-10;邮箱格式必须正确;年龄在18-60之间

    @RestController
    @RequestMapping("/user")
    public class UserController {
        
        @PostMapping("/add")
        public String addUser(UserDto user) {
            // 手动校验参数
            if (user.getName() == null || user.getName().trim().isEmpty()) {
                return "用户名不能为空";
            }
            if (user.getName().length() < 5 || user.getName().length() > 10) {
                return "用户名长度必须在5-10之间";
            }
            if (user.getAge() == null) {
                return "年龄不能为空";
            }
            if (user.getAge() < 18 || user.getAge() > 60) {
                return "年龄必须在18-60之间";
            }
            if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
                return "邮箱不能为空";
            }
            if (!user.getEmail().matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
                return "邮箱格式不正确";
            }
            
            // 业务逻辑处理
            return "用户添加成功";
        }
    }
    
    
  • 总结

    1. 代码冗长,不利于维护
    2. 每个接口都要写重复的校验逻辑
    3. 校验逻辑和业务逻辑耦合,不够优雅

使用 Validation 的优雅写法

可以在实体类上加注解,把校验规则声明在模型上,让 Spring 自动完成校验

maven 依赖

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

UserDto对象加上注解

import javax.validation.constraints.*;

public class UserDto {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 10, message = "用户名长度必须在{min}-{max}之间")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确") // 自带邮箱格式校验,无需自己写正则!
    private String email;

    @NotNull(message = "年龄不能为空")
    @Min(value = 0, message = "年龄最小为{value}")
    @Max(value = 150, message = "年龄最大为{value}")
    private Integer age;

    // 省略 Getter 和 Setter...
}

在Controller参数前加@Valid或@Validated注解

import javax.validation.Valid;

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/add")
    // 关键一步:在 @RequestBody 前加上 @Valid 注解
    public String addUser(@Valid @RequestBody UserDto user) {
        // 只需关注核心业务
        System.out.println("用户创建成功: " + user);
        return "success";
    }
}

通过上述使用 validation 改造,Spring 会自动对 UserDto 的字段进行校验,当请求参数不满足规则时,Spring Boot 会自动抛出 MethodArgumentNotValidException 异常,不会进入这个方法体。但我们不能直接给用户返回异常栈,需要统一处理

全局异常处理(友好返回错误信息)

刚才我们已经说过了参数校验不满足规则,系统会抛出MethodArgumentNotValidException ,那么我们就可以通过 @RestControllerAdvice 捕获 MethodArgumentNotValidException,来实现统一返回错误信息

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理实体校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, Object> handleValidException(MethodArgumentNotValidException e) {
        Map<String, Object> errorResult = new HashMap<>();
        errorResult.put("code", 400);
        errorResult.put("message", "参数校验失败");
        // 从异常对象中拿到具体的错误信息
        // 这里只取第一个错误信息,也可以全部返回
        String defaultMessage = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
        errorResult.put("data", defaultMessage);
        return errorResult;
    }
}

常用注解

注解 功能说明
@NotNull 值不能为null
@NotBlank 字符串不能为空(trim后长度>0)
@NotEmpty 集合、数组、Map、String不能为空
@Size(min=, max=) 检查字符串、集合、数组大小
@Min(value) 数字最小值
@Max(value) 数字最大值
@Email 校验邮箱格式
@Pattern(regexp=) 正则表达式匹配
@Positive 正数
@Future 日期必须在未来
@Past 日期必须在过去

分组校验

当同一个实体类在不同场景下有不同的校验规则时,比如新增时ID应为空,而更新时ID不能为空,这时就需要分组校验

定义分组接口(标记接口)

public interface CreateGroup {} // 创建分组
public interface UpdateGroup {} // 更新分组

在实体上指定分组
继续改造一下我们的UserDto,这时候需要增加id字段

public class UserDto {
    @Null(groups = CreateGroup.class, message = "创建时ID必须为空")
    @NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
    private Long id;

    @NotBlank(message = "用户名不能为空", groups = {CreateGroup.class, UpdateGroup.class})
    private String username;
    // ... 其他字段
}

在Controller中使用@Validated指定分组

@PostMapping("/create")
public String create(@Validated(CreateGroup.class) @RequestBody UserDto user) {
    // ... 创建逻辑
}

@PostMapping("/update")
public String update(@Validated(UpdateGroup.class) @RequestBody UserDto user) {
    // ... 更新逻辑
}
posted @ 2025-09-19 22:25  小郑[努力版]  阅读(6)  评论(0)    收藏  举报