Spring Boot 3.x + JDK17 参数校验全场景实战(含List列表_嵌套_分组)

Spring Boot 3.x + JDK17 参数校验全场景实战

在日常开发中,接口参数校验是保障数据合法性、减少业务异常的关键环节。如果用传统的if-else逐一判断,不仅代码冗余、可读性差,还难以维护。Spring Boot 3.x 整合 Spring Validation(基于JSR-380规范,底层依赖Hibernate Validator 8.x,适配JDK17),提供注解驱动的校验方式,可快速适配单参数、实体类、List列表、嵌套对象等多场景,搭配全局异常处理与自定义校验,轻松搭建标准化校验体系。

本文适配JDK17+SpringBoot3.x环境,覆盖参数校验全场景,重点细化分组校验、完善Service层校验,附完整可复用代码,新手也能直接上手。

一、环境准备(Maven依赖,适配SpringBoot3.x+JDK17)

Spring Boot 3.x 需显式引入校验 starter,依赖适配JDK17的Hibernate Validator 8.x(无需手动指定版本,Spring Boot父工程已管理),Maven配置如下:

xml 复制代码
<!-- 参数校验核心依赖,SpringBoot3.x 适配JDK17 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 可选:用于返回标准化结果,可替换为自身项目的响应封装 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意:SpringBoot3.x 要求 Hibernate Validator 最低版本为8.x,该版本兼容JDK11+,完美适配JDK17;若手动指定版本,需避免版本冲突(推荐使用Spring Boot父工程管理的版本)。

二、核心校验注解(必记)

先掌握常用注解,覆盖绝大多数基础校验场景,后续所有实战都基于这些注解扩展,适配JDK17+SpringBoot3.x无差异:

注解 作用 适用类型
@NotNull 非空(仅判断是否为null,不判断空字符串/空集合) 所有引用类型(String、Integer、List等)
@NotBlank 非空且去空格后长度>0(仅针对字符串) String
@NotEmpty 非空且元素个数>0(字符串/集合/数组) String、List、数组
@Min(value) 数值不小于指定值 Integer、Long、BigDecimal等数字类型
@Max(value) 数值不大于指定值 数字类型
@Size(min,max) 长度/个数在指定范围 String、List、数组
@Email 符合邮箱格式 String
@Pattern(regexp) 匹配指定正则表达式 String

三、基础校验场景(单参数+普通实体)

3.1 单参数校验(Controller层)

适用于接口参数较少(1-2个)的场景,需在Controller类上添加@Validated注解,开启方法参数校验,直接在参数上添加校验注解即可,适配JDK17+SpringBoot3.x无差异。

java 复制代码
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

@RestController
@RequestMapping("/user")
@Validated // 开启单参数校验,必须加在类上
public class UserController {

    // 路径参数校验(ID≥1)
    @GetMapping("/{id}")
    public String getUserById(@PathVariable @Min(1) Long id) {
        return "查询用户:" + id;
    }

    // 请求参数校验(用户名非空)
    @GetMapping("/query")
    public String queryUser(@RequestParam @NotBlank(message = "用户名不能为空") String username) {
        return &#34;查询用户:&#34; + username;&#xA;    }&#xA;}

3.2 普通实体类校验(最常用)

适用于接口参数较多的场景,将参数封装为DTO实体类,给字段添加校验注解,Controller入参用@Valid触发校验。

步骤1:定义DTO实体类
java 复制代码
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data // Lombok简化get/set,可手动实现,适配JDK17
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度为2-20位")
    private String username;

    @NotNull(message = "年龄不能为空")
    @Min(1)
    @Max(150)
    private Integer age;

    @Email(message = "邮箱格式错误")
    private String email;
}
步骤2:Controller触发校验
java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;

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

    // 实体类参数校验,@Valid触发校验
    @PostMapping("/create")
    public Result<?> createUser(@RequestBody @Valid UserDTO userDTO) {
        // 业务逻辑:调用Service保存用户
        return Result.success("用户创建成功");
    }
}

四、全局异常处理(友好响应,适配SpringBoot3.x)

参数校验失败会抛出两种核心异常,需通过全局异常处理器捕获,返回标准化响应(避免直接返回默认错误信息,提升接口友好度)。SpringBoot3.x 中异常处理逻辑无差异,仅需确保依赖适配。

先定义统一响应类Result:

java 复制代码
import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
public class Result<T> {
    // 状态码:200成功,400参数错误,500系统错误
    private Integer code;
    // 响应信息
    private String msg;
    // 响应数据
    private T data;

    // 成功响应(无数据)
    public static <T> Result<T> success(String msg) {
        Result<T> result = new Result<>();
        result.setCode(HttpStatus.OK.value());
        result.setMsg(msg);
        return result;
    }

    // 成功响应(带数据)
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(HttpStatus.OK.value());
        result.setMsg("操作成功");
        result.setData(data);
        return result;
    }

    // 失败响应
    public static <T> Result<T> fail(Integer code, String msg) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

再编写全局异常处理器:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.stream.Collectors;

@RestControllerAdvice // 全局异常处理,作用于所有@RestController
@Slf4j
public class GlobalExceptionHandler {

    // 处理实体类参数校验异常(@RequestBody + @Valid)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        // 获取第一个错误信息(也可收集所有错误信息)
        String msg = bindingResult.getFieldError().getDefaultMessage();
        log.error("参数校验失败:{}", msg);
        return Result.fail(HttpStatus.BAD_REQUEST.value(), msg);
    }

    // 处理单参数校验异常(@Validated 方法参数)
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<?> handleConstraintException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        // 收集所有错误信息(拼接为字符串)
        String msg = violations.stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(","));
        log.error("参数校验失败:{}", msg);
        return Result.fail(HttpStatus.BAD_REQUEST.value(), msg);
    }
}

五、重点:List列表参数校验(多场景适配)

开发中常遇到"实体类包含List集合"的场景(如订单包含多个商品、批量新增用户),核心要点:在List字段上添加@Valid注解,触发集合内元素的逐个校验,配合集合本身的非空/长度注解,实现双层校验,适配JDK17+SpringBoot3.x无差异。

5.1 场景1:实体类包含List<对象>(最常用)

步骤1:定义List元素的实体类(如商品DTO)
java 复制代码
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.math.BigDecimal;

@Data
public class ProductDTO {
    @NotBlank(message = "商品名称不能为空")
    @Size(min = 2, max = 50, message = "商品名称长度2-50位")
    private String productName;

    @NotNull(message = "商品价格不能为空")
    @Min(value = 1, message = "商品价格不能小于1")
    private BigDecimal price;

    @NotNull(message = "购买数量不能为空")
    @Min(value = 1, message = "购买数量不能小于1")
    private Integer num;
}
步骤2:定义包含List的主实体类(如订单DTO)

List字段需添加两层注解:① @Valid:触发集合内每个元素的校验;② @NotEmpty/@Size:校验集合本身。

java 复制代码
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;

@Data
public class OrderDTO {
    @NotBlank(message = "订单号不能为空")
    private String orderNo;

    @NotBlank(message = "用户ID不能为空")
    private String userId;

    // 核心:@Valid 触发List内每个ProductDTO的校验
    // @NotEmpty 校验List非空,@Size 限制元素个数
    @Valid
    @NotEmpty(message = "商品列表不能为空")
    @Size(min = 1, max = 10, message = "商品数量不能超过10个")
    private List<ProductDTO> productList;
}
步骤3:Controller触发校验

与普通实体校验一致,只需给主实体加@Valid即可,无需额外配置。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;

@RestController
@RequestMapping("/order")
public class OrderController {

    @PostMapping("/create")
    public Result<?> createOrder(@RequestBody @Valid OrderDTO orderDTO) {
        // 业务逻辑:保存订单及关联商品
        return Result.success("订单创建成功");
    }
}

5.2 场景2:嵌套列表校验(List<List<对象>>)

如果是二维列表(如批次订单包含多个订单,每个订单包含多个商品),只需在每一层List字段上都添加@Valid注解,实现递归全链路校验。

java 复制代码
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;

// 最外层:批次订单DTO
@Data
public class BatchOrderDTO {
    @NotBlank(message = "批次号不能为空")
    private String batchNo;

    // 第一层List:@Valid触发内层OrderItemDTO的校验
    @Valid
    @NotEmpty(message = "订单列表不能为空")
    private List<OrderItemDTO> orderItemList;
}

// 中间层:订单项DTO
@Data
public class OrderItemDTO {
    @NotBlank(message = "子订单号不能为空")
    private String subOrderNo;

    // 第二层List:@Valid触发ProductDTO的校验
    @Valid
    @NotEmpty(message = "商品列表不能为空")
    private List<ProductDTO> productList;
}

5.3 场景3:List<基础类型>校验(List/List)

基础类型(String、Long等)无法直接给字段加注解,需通过@Pattern或自定义注解实现校验。

方式1:@Pattern + 正则(适合简单规则)

直接在List字段上加@Pattern,正则会匹配集合中的每个元素,配合@NotEmpty校验集合非空。

java 复制代码
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.util.List;

@Data
public class UserBatchDTO {
    @NotBlank(message = "操作人ID不能为空")
    private String operatorId;

    // 校验:手机号列表非空,且每个手机号符合格式
    @Valid
    @NotEmpty(message = "手机号列表不能为空")
    @Pattern(regexp = "1[3-9]\\d{9}", message = "手机号格式错误")
    private List<String> phoneList;

    // 校验:ID列表非空,且每个ID为正整数
    @Valid
    @NotEmpty(message = "ID列表不能为空")
    @Pattern(regexp = "^[1-9]\\d*$", message = "ID必须为正整数")
    private List<String> idList;
}
方式2:自定义注解(适合复杂规则,推荐)

如果校验规则复杂(如身份证、银行卡号),自定义注解更灵活,示例实现"手机号列表校验"。

步骤1:定义自定义注解
java 复制代码
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.PARAMETER}) // 作用于字段/参数
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Constraint(validatedBy = PhoneListValidator.class) // 绑定校验器
public @interface PhoneList {
    // 校验失败提示信息
    String message() default "手机号列表格式错误";

    // 分组校验相关,默认空
    Class<?>[] groups() default {};

    // 负载信息,默认空
    Class<? extends Payload>[] payload() default {};
}
步骤2:实现校验器
java 复制代码
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
import java.util.regex.Pattern;

// 泛型:第一个是自定义注解,第二个是要校验的类型(List<String>)
public class PhoneListValidator implements ConstraintValidator<PhoneList, List<String>> {
    // 手机号正则表达式
    private static final Pattern PHONE_PATTERN = Pattern.compile("1[3-9]\\d{9}");

    @Override
    public boolean isValid(List<String> phoneList, ConstraintValidatorContext context) {
        // 1. 列表为null/空,返回false(可配合@NotEmpty双重保障)
        if (phoneList == null || phoneList.isEmpty()) {
            return false;
        }
        // 2. 遍历每个手机号,校验格式
        for (String phone : phoneList) {
            if (phone == null || !PHONE_PATTERN.matcher(phone).matches()) {
                return false;
            }
        }
        return true;
    }
}
步骤3:使用自定义注解
java 复制代码
import lombok.Data;
import javax.validation.constraints.NotBlank;

@Data
public class UserBatchDTO {
    @NotBlank(message = "操作人ID不能为空")
    private String operatorId;

    // 直接使用自定义注解,简洁高效
    @PhoneList(message = "手机号列表不能为空且格式必须为1[3-9]\\d{9}")
    private List<String> phoneList;
}

六、进阶用法:分组校验(细化版,适配SpringBoot3.x+JDK17)

同一DTO可能适配"新增""更新""查询"等不同场景(如新增时ID为空、更新时ID非空、查询时ID可选),此时需用分组校验。核心要点:仅Spring扩展的@Validated支持分组,@Valid不支持;SpringBoot3.x+JDK17无额外配置,仅需遵循分组校验规范,以下细化全场景用法。

6.1 核心概念

分组校验本质是通过"分组接口"给校验注解分类,触发校验时指定分组,仅执行该分组下的校验规则。分组接口无需实现,仅作为标识,可继承扩展(实现分组复用)。

6.2 步骤1:定义分组接口(基础+继承扩展)

推荐按业务场景定义分组,支持继承(如查询分组继承公共分组),减少重复注解:

java 复制代码
// 公共分组:所有场景都需校验的规则(如用户名、年龄)
public interface CommonGroup {}

// 新增分组:继承公共分组,新增专属规则
public interface AddGroup extends CommonGroup {}

// 更新分组:继承公共分组,更新专属规则
public interface UpdateGroup extends CommonGroup {}

// 查询分组:单独定义(仅校验必要参数,无需继承公共分组)
public interface QueryGroup {}

6.3 步骤2:实体类字段指定分组(多场景适配)

给每个字段的校验注解指定分组,可指定多个分组(如同时适配新增和更新),未指定分组的注解默认不执行(需注意避坑):

java 复制代码
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Null;
import javax.validation.constraints.Size;

@Data
public class UserDTO {
    // 新增:ID必须为空(AddGroup专属)
    // 更新:ID必须≥1(UpdateGroup专属)
    // 查询:ID可选(无需校验,不指定分组)
    @Null(groups = AddGroup.class, message = "新增时ID必须为空")
    @Min(value = 1, groups = UpdateGroup.class, message = "更新时ID必须≥1")
    private Long id;

    // 公共规则:所有继承CommonGroup的场景(新增、更新)都需校验
    @NotBlank(groups = CommonGroup.class, message = "用户名不能为空")
    @Size(min = 2, max = 20, groups = CommonGroup.class, message = "用户名长度2-20位")
    private String username;

    // 公共规则:新增、更新都需校验
    @Min(value = 1, groups = CommonGroup.class, message = "年龄不能小于1")
    private Integer age;

    // 新增需校验,更新可选(仅AddGroup)
    @NotBlank(groups = AddGroup.class, message = "新增时邮箱不能为空")
    private String email;

    // 查询专属:仅查询时校验手机号格式(QueryGroup)
    @Pattern(regexp = "1[3-9]\\d{9}", groups = QueryGroup.class, message = "查询时手机号格式错误")
    private String phone;
}

6.3 步骤3:Controller指定分组校验(单实体+List列表)

场景A:普通实体分组校验

用@Validated(分组接口.class)指定分组,触发对应规则,继承分组会自动执行父分组的校验规则:

java 复制代码
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

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

    // 新增用户:触发AddGroup(含CommonGroup公共规则)
    @PostMapping("/add")
    public Result<?> addUser(@RequestBody @Validated(AddGroup.class) UserDTO userDTO) {
        return Result.success("用户新增成功");
    }

    // 更新用户:触发UpdateGroup(含CommonGroup公共规则)
    @PutMapping("/update")
    public Result<?> updateUser(@RequestBody @Validated(UpdateGroup.class) UserDTO userDTO) {
        return Result.success("用户更新成功");
    }

    // 查询用户:触发QueryGroup(仅查询专属规则)
    @GetMapping("/query")
    public Result<?> queryUser(@Validated(QueryGroup.class) UserDTO userDTO) {
        return Result.success("查询成功");
    }
}
场景B:List列表的分组校验

列表分组校验与普通实体一致,仅需在Controller指定分组,List字段的@Valid会自动触发集合内元素的对应分组校验:

java 复制代码
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

// 批量用户DTO(包含List<UserDTO>)
@Data
public class BatchUserDTO {
    @NotBlank(message = "操作人ID不能为空")
    private String operatorId;

    // 核心:@Valid 触发List内每个UserDTO的分组校验
    @Valid
    @NotEmpty(message = "用户列表不能为空")
    private List<UserDTO> userList;
}

// Controller 批量新增(触发AddGroup分组)
@PostMapping("/batch/add")
public Result<?> batchAddUser(@RequestBody @Validated(AddGroup.class) BatchUserDTO batchUserDTO) {
    return Result.success("批量新增用户成功");
}

6.4 步骤4:分组校验避坑要点

  1. 未指定分组的校验注解,任何分组触发时都不会执行,需手动指定分组(或默认分组,下文说明)。

  2. 分组继承时,子分组会执行自身+父分组的所有校验规则,无需重复给字段加注解。

  3. 默认分组:若校验注解不指定groups,默认属于javax.validation.groups.Default分组,触发时需用@Validated(Default.class)。

  4. 多分组触发:可指定多个分组,如@Validated({AddGroup.class, QueryGroup.class}),会执行两个分组的所有规则。

七、进阶用法:嵌套对象校验

实体类中包含另一个实体类(非List),需在嵌套字段上添加@Valid注解,触发递归校验,与列表校验的核心逻辑一致,适配分组校验无差异。

java 复制代码
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class OrderDTO {
    @NotBlank(message = "订单号不能为空")
    private String orderNo;

    // 嵌套对象:收件人信息,@Valid触发校验(支持分组校验)
    @Valid
    @NotNull(message = "收件人信息不能为空")
    private ReceiverDTO receiver;
}

// 嵌套的收件人DTO(可指定分组)
@Data
public class ReceiverDTO {
    @NotBlank(groups = CommonGroup.class, message = "收件人姓名不能为空")
    private String name;

    @Pattern(regexp = "1[3-9]\\d{9}", groups = CommonGroup.class, message = "手机号格式错误")
    private String phone;

    @NotBlank(groups = AddGroup.class, message = "新增订单需填写收件地址")
    private String address;
}

八、@Valid 与 @Validated 区别(关键避坑)

很多开发者混淆两者,记住核心区别,避免校验失效,适配SpringBoot3.x+JDK17无差异:

特性 @Valid @Validated
来源 JSR-380 标准注解 Spring 扩展注解
分组校验 不支持 支持(核心优势)
作用范围 字段、方法参数、嵌套对象 类、方法、参数
嵌套校验 支持(需显式加@Valid) 需配合@Valid触发嵌套

九、Service层校验(完善版,适配SpringBoot3.x+JDK17)

Controller层校验仅针对接口入参,若Service层手动创建实体对象、接收非接口传入的参数(如定时任务、内部调用),需手动触发校验。SpringBoot3.x提供两种常用实现方式,均适配JDK17。

9.1 方式1:注入Validator接口(手动校验,推荐)

通过Validator接口手动校验实体,可指定分组,灵活适配各种业务场景,无需额外配置:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;

@Service
@Validated // 可选:开启Service层方法参数的自动校验
public class UserService {
    // 注入Validator接口(SpringBoot自动配置,无需手动创建)
    @Autowired
    private Validator validator;

    // 手动校验实体(无分组)
    public void saveUser(UserDTO dto) {
        // 手动校验,无分组时校验默认分组(Default.class)
        Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto);
        handleValidationResult(violations);
        // 业务逻辑
    }

    // 手动校验实体(指定分组,如新增分组)
    public void addUser(UserDTO dto) {
        // 第二个参数指定分组,可指定多个(如new Class[]{AddGroup.class})
        Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto, AddGroup.class);
        handleValidationResult(violations);
        // 业务逻辑
    }

    // 手动校验List列表(指定分组)
    public void batchAddUser(List<UserDTO> userList) {
        for (UserDTO dto : userList) {
            Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto, AddGroup.class);
            handleValidationResult(violations);
        }
        // 业务逻辑
    }

    // 统一处理校验结果:抛出异常,交给全局异常处理器捕获
    private void handleValidationResult(Set<ConstraintViolation<UserDTO>> violations) {
        if (!violations.isEmpty()) {
            // 拼接所有错误信息
            String errorMsg = violations.stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(","));
            // 抛出异常,可自定义异常,也可抛出IllegalArgumentException
            throw new IllegalArgumentException("Service层参数校验失败:" + errorMsg);
        }
    }
}

9.2 方式2:@Validated + 方法参数注解(自动校验)

在Service类上添加@Validated,给方法参数加校验注解/指定分组,Spring会自动触发校验,失败抛出ConstraintViolationException,可被全局异常处理器捕获:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Min;
import java.util.List;

@Service
@Validated // 开启Service层方法参数的自动校验
public class OrderService {
    // 1. 单参数自动校验(指定分组)
    public void checkOrderNo(@NotBlank(groups = QueryGroup.class) String orderNo) {
        // 业务逻辑
    }

    // 2. 实体类自动校验(指定分组)
    public void saveOrder(@Valid @Validated(AddGroup.class) OrderDTO orderDTO) {
        // @Valid 触发嵌套校验(如ReceiverDTO),@Validated指定分组
        // 业务逻辑
    }

    // 3. List列表自动校验(指定分组)
    public void batchSaveOrder(@Valid @Validated(AddGroup.class) List<OrderDTO> orderList) {
        // @Valid 触发列表内每个OrderDTO的校验,@Validated指定分组
        // 业务逻辑
    }
}

9.3 Service层校验避坑要点

  1. 方式2(@Validated自动校验)仅适用于"方法参数",若实体是Service内部创建(如new UserDTO()),无法触发自动校验,需用方式1手动校验。

  2. 手动校验时,若需校验嵌套对象/列表,无需额外操作,Validator接口会自动识别@Valid注解,触发递归校验。

  3. 全局异常处理器需捕获IllegalArgumentException(方式1手动抛出)和ConstraintViolationException(方式2自动抛出),已有处理器可直接适配。

十、常见避坑指南(必看,适配SpringBoot3.x+JDK17)

  1. 单参数校验失效:未在Controller/Service类上添加@Validated,仅在参数上加注解无效。

  2. 列表元素校验失效:忘记给List字段加@Valid,仅加@NotEmpty只能校验集合非空,无法校验内部元素。

  3. 嵌套校验失效:嵌套对象字段未加@Valid,无论用@Valid还是@Validated,都无法触发递归校验。

  4. 分组校验失效:未给校验注解指定分组,或触发时指定的分组与注解分组不匹配;未用@Validated(@Valid不支持分组)。

  5. 基础类型List校验错误:直接给List加@NotBlank,会导致校验失效,需用@Pattern或自定义注解。

  6. SpringBoot3.x版本冲突:手动指定Hibernate Validator版本低于8.x,会导致适配JDK17失败,推荐使用Spring Boot父工程管理的版本。

  7. Service层自动校验失效:实体是内部创建,未用手动校验;或未在Service类上添加@Validated。

十一、总结

本文适配JDK17+SpringBoot3.x环境,覆盖参数校验全场景,重点细化分组校验(含继承、多场景适配)、完善Service层双校验方式,核心是"注解驱动+全局异常处理",无需冗余的if-else判断,即可实现标准化校验。

核心流程:引入适配依赖→添加校验注解→指定分组(可选)→开启校验(@Valid/@Validated)→全局异常处理→Service层手动/自动校验兜底,搭配避坑要点,可适配绝大多数业务场景,提升代码简洁度与接口安全性。

相关推荐
闻哥2 小时前
从 SQL 执行到优化器内核:MySQL 性能调优核心知识点解析
java·jvm·数据库·spring boot·sql·mysql·面试
星月前端2 小时前
springboot中使用LibreOffice实现word转pdf(还原程度很高,可以配置线程并发!)
spring boot·pdf·word
qq_171520352 小时前
linux服务器springboot(docker)项目word转pdf中文乱码
linux·spring boot·docker·pdf·word
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于spring boot的摩托车合格证管理系统为例,包含答辩的问题和答案
java·spring boot·后端
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 基于spring boot的国学诗词网站设计与实现--为例,包含答辩的问题和答案
java·spring boot·后端
奋进的芋圆3 小时前
Spring Boot 3.x 企业级 SSO 单点登录实现指南
spring boot·spring cloud
千寻技术帮3 小时前
10410_基于Springboot的文化旅游宣传网站
spring boot·后端·vue·源码·旅游·安装·在线旅游
源码宝3 小时前
前后端分离架构:不良事件管理系统源码(Vue2+Element UI+Laravel 8)
后端·php·源码·二次开发·程序·不良事件上报·医院不良事件管理系统