最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java Jakarta Validation 實(shí)戰(zhàn)指南

 更新時間:2026年07月17日 09:23:52   作者:唐青楓  
Jakarta Validation 是 Java 生態(tài)里用于數(shù)據(jù)校驗(yàn)的標(biāo)準(zhǔn)規(guī)范, 它最常見的使用方式,就是在請求對象、實(shí)體對象、方法參數(shù)上加注解,下面就來詳細(xì)的了解一下

簡介

Jakarta Validation 是 Java 生態(tài)里用于數(shù)據(jù)校驗(yàn)的標(biāo)準(zhǔn)規(guī)范。

它最常見的使用方式,就是在請求對象、實(shí)體對象、方法參數(shù)上加注解:

public record CreateUserRequest(
        @NotBlank(message = "用戶名不能為空")
        String username,

        @Email(message = "郵箱格式不正確")
        String email,

        @Min(value = 18, message = "年齡不能小于18歲")
        Integer age
) {
}

然后在接口入口處觸發(fā)校驗(yàn):

@PostMapping("/users")
public Long create(@Valid @RequestBody CreateUserRequest request) {
    return userService.create(request);
}

請求參數(shù)不符合規(guī)則時,Spring 會在進(jìn)入業(yè)務(wù)方法前拋出校驗(yàn)異常。

它的價值很直接:把參數(shù)規(guī)則寫在模型上,把錯誤返回集中處理,讓業(yè)務(wù)代碼少寫一堆重復(fù)的 if 判斷。

Jakarta Validation、Hibernate Validator、Spring Boot 的關(guān)系

這幾個名字經(jīng)常一起出現(xiàn),但角色不一樣。

名稱角色說明
Jakarta Validation規(guī)范定義注解、API、校驗(yàn)?zāi)P?/td>
Hibernate Validator實(shí)現(xiàn)Jakarta Validation 的常見參考實(shí)現(xiàn)
Spring Boot集成方自動配置 Validator,并在 Web、Service 等場景觸發(fā)校驗(yàn)

簡單理解:

Jakarta Validation 定規(guī)則
Hibernate Validator 負(fù)責(zé)執(zhí)行規(guī)則
Spring Boot 負(fù)責(zé)把校驗(yàn)接到 Web 請求、方法調(diào)用和異常處理流程里

包名也有一個歷史變化:

時代包名
Java EE / Bean Validation 老項(xiàng)目javax.validation.*
Jakarta EE / Spring Boot 3+jakarta.validation.*

Spring Boot 3 開始整體切到 Jakarta 命名空間,所以代碼里通常使用:

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;

Maven 依賴

Spring Boot 項(xiàng)目通常直接引入 validation starter:

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

它會帶上 Jakarta Validation API 和 Hibernate Validator。

如果是普通 Java 項(xiàng)目,可以直接引入 Hibernate Validator:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${hibernate-validator.version}</version>
</dependency>

部分 Java SE 場景還需要表達(dá)式語言實(shí)現(xiàn),用于處理更復(fù)雜的消息插值:

<dependency>
    <groupId>org.glassfish.expressly</groupId>
    <artifactId>expressly</artifactId>
    <version>${expressly.version}</version>
</dependency>

Spring Boot 項(xiàng)目優(yōu)先交給 Boot 的依賴管理,不建議在業(yè)務(wù)項(xiàng)目里隨意手寫 Hibernate Validator 版本。

第一個 Spring Boot Demo

先準(zhǔn)備一個創(chuàng)建用戶請求對象。

package com.example.user.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record CreateUserRequest(

        @NotBlank(message = "用戶名不能為空")
        @Size(min = 2, max = 20, message = "用戶名長度需要在2到20之間")
        String username,

        @NotBlank(message = "郵箱不能為空")
        @Email(message = "郵箱格式不正確")
        String email,

        @NotNull(message = "年齡不能為空")
        @Min(value = 18, message = "年齡不能小于18歲")
        @Max(value = 100, message = "年齡不能大于100歲")
        Integer age,

        @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手機(jī)號格式不正確")
        String phone
) {
}

Controller:

package com.example.user.controller;

import com.example.user.dto.CreateUserRequest;
import com.example.user.dto.UserCreateResponse;
import com.example.user.service.UserService;
import jakarta.validation.Valid;
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;

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

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public UserCreateResponse create(@Valid @RequestBody CreateUserRequest request) {
        Long userId = userService.create(request);
        return new UserCreateResponse(userId);
    }
}

響應(yīng)對象:

package com.example.user.dto;

public record UserCreateResponse(Long userId) {
}

Service:

package com.example.user.service;

import com.example.user.dto.CreateUserRequest;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public Long create(CreateUserRequest request) {
        return System.currentTimeMillis();
    }
}

請求參數(shù)不合法時,Controller 方法不會繼續(xù)執(zhí)行,Spring 會拋出綁定異常。

常用注解

常用約束可以按類型記。

注解適用場景說明
@NotNull任意對象不能為 null
@NotEmpty字符串、集合、數(shù)組、Map不能為 null,長度或大小不能為 0
@NotBlank字符串不能為 null,去掉空白后不能是空字符串
@Size字符串、集合、數(shù)組、Map限制長度或大小
@Min / @Max整數(shù)、長整數(shù)等數(shù)字限制最小值和最大值
@DecimalMin / @DecimalMaxBigDecimal 等數(shù)字適合金額、比例
@Positive數(shù)字需要大于 0
@PositiveOrZero數(shù)字需要大于等于 0
@Negative數(shù)字需要小于 0
@Digits數(shù)字限制整數(shù)位和小數(shù)位
@Email字符串郵箱格式
@Pattern字符串正則表達(dá)式
@Past日期時間應(yīng)為過去時間
@PastOrPresent日期時間應(yīng)為過去或當(dāng)前時間
@Future日期時間應(yīng)為未來時間
@FutureOrPresent日期時間應(yīng)為未來或當(dāng)前時間
@AssertTrue布爾值應(yīng)為 true
@AssertFalse布爾值應(yīng)為 false

幾個注解很容易混:

注解null空字符串 ""空白字符串 " "
@NotNull不通過通過通過
@NotEmpty不通過不通過通過
@NotBlank不通過不通過不通過

用戶名、標(biāo)題、備注這類字符串必填字段,通常用 @NotBlank。

集合必填并且至少有一個元素,通常用 @NotEmpty。

數(shù)字、日期、對象必填,通常用 @NotNull。

@Valid 和 @Validated 的區(qū)別

@Valid 來自 Jakarta Validation:

import jakarta.validation.Valid;

@Validated 來自 Spring:

import org.springframework.validation.annotation.Validated;

常見區(qū)別如下:

注解來源常見用途
@ValidJakarta Validation觸發(fā)對象校驗(yàn)、級聯(lián)校驗(yàn)
@ValidatedSpring觸發(fā)分組校驗(yàn)、方法參數(shù)校驗(yàn)

JSON 請求體校驗(yàn)通常這樣寫:

@PostMapping
public UserCreateResponse create(@Valid @RequestBody CreateUserRequest request) {
    return new UserCreateResponse(1001L);
}

如果要使用分組校驗(yàn),通常用 @Validated

@PostMapping
public UserCreateResponse create(
        @Validated(CreateGroup.class) @RequestBody UserRequest request
) {
    return new UserCreateResponse(1001L);
}

如果要校驗(yàn) @RequestParam、@PathVariable 這種普通參數(shù),Controller 類或方法通常需要加 @Validated

package com.example.user.controller;

import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @GetMapping("/{id}")
    public String getById(@PathVariable @Min(value = 1, message = "用戶ID需要大于0") Long id) {
        return "user-" + id;
    }
}

全局異常處理

參數(shù)校驗(yàn)失敗后,默認(rèn)錯誤響應(yīng)通常不適合直接給前端使用。

可以用 @RestControllerAdvice 統(tǒng)一包裝錯誤結(jié)果。

先定義統(tǒng)一錯誤對象:

package com.example.common;

import java.util.List;

public record ApiErrorResponse(
        String code,
        String message,
        List<FieldErrorItem> errors
) {
}

字段錯誤對象:

package com.example.common;

public record FieldErrorItem(
        String field,
        String message
) {
}

全局異常處理器:

package com.example.common;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;

import java.util.ArrayList;
import java.util.List;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleRequestBodyValidException(MethodArgumentNotValidException ex) {
        List<FieldErrorItem> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(this::toFieldErrorItem)
                .toList();

        return new ApiErrorResponse("VALIDATION_FAILED", "參數(shù)校驗(yàn)失敗", errors);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleConstraintViolationException(ConstraintViolationException ex) {
        List<FieldErrorItem> errors = ex.getConstraintViolations()
                .stream()
                .map(this::toFieldErrorItem)
                .toList();

        return new ApiErrorResponse("VALIDATION_FAILED", "參數(shù)校驗(yàn)失敗", errors);
    }

    @ExceptionHandler(HandlerMethodValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleHandlerMethodValidationException(HandlerMethodValidationException ex) {
        List<FieldErrorItem> errors = new ArrayList<>();

        ex.visitResults(new HandlerMethodValidationException.Visitor() {
            @Override
            public void other(org.springframework.validation.method.ParameterValidationResult result) {
                result.getResolvableErrors().forEach(error -> {
                    String field = result.getMethodParameter().getParameterName();
                    errors.add(new FieldErrorItem(field, error.getDefaultMessage()));
                });
            }
        });

        return new ApiErrorResponse("VALIDATION_FAILED", "參數(shù)校驗(yàn)失敗", errors);
    }

    private FieldErrorItem toFieldErrorItem(FieldError error) {
        return new FieldErrorItem(error.getField(), error.getDefaultMessage());
    }

    private FieldErrorItem toFieldErrorItem(ConstraintViolation<?> violation) {
        return new FieldErrorItem(
                violation.getPropertyPath().toString(),
                violation.getMessage()
        );
    }
}

常見異常來源:

異常常見來源
MethodArgumentNotValidException@RequestBody 對象校驗(yàn)失敗
ConstraintViolationExceptionService 方法參數(shù)校驗(yàn)、部分普通參數(shù)校驗(yàn)
HandlerMethodValidationExceptionSpring MVC 方法參數(shù)校驗(yàn)失敗

不同 Spring 版本和注解位置可能拋出不同異常,統(tǒng)一處理時可以一起覆蓋。

分組校驗(yàn)

新增用戶和修改用戶經(jīng)常有不同規(guī)則。

比如新增時沒有 id,修改時需要傳 id。

先定義兩個分組接口:

package com.example.user.validation;

public interface CreateGroup {
}
package com.example.user.validation;

public interface UpdateGroup {
}

請求對象:

package com.example.user.dto;

import com.example.user.validation.CreateGroup;
import com.example.user.validation.UpdateGroup;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record UserRequest(

        @NotNull(message = "用戶ID不能為空", groups = UpdateGroup.class)
        Long id,

        @NotBlank(message = "用戶名不能為空", groups = {CreateGroup.class, UpdateGroup.class})
        @Size(min = 2, max = 20, message = "用戶名長度需要在2到20之間", groups = {CreateGroup.class, UpdateGroup.class})
        String username,

        @NotBlank(message = "郵箱不能為空", groups = CreateGroup.class)
        @Email(message = "郵箱格式不正確", groups = {CreateGroup.class, UpdateGroup.class})
        String email
) {
}

Controller:

package com.example.user.controller;

import com.example.user.dto.UserRequest;
import com.example.user.validation.CreateGroup;
import com.example.user.validation.UpdateGroup;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @PostMapping
    public String create(@Validated(CreateGroup.class) @RequestBody UserRequest request) {
        return "創(chuàng)建成功";
    }

    @PutMapping
    public String update(@Validated(UpdateGroup.class) @RequestBody UserRequest request) {
        return "修改成功";
    }
}

分組適合規(guī)則差異比較明確的場景。

如果新增和修改字段差異很大,拆成 CreateUserRequestUpdateUserRequest 兩個 DTO 通常更清楚。

級聯(lián)校驗(yàn)

對象里嵌套對象時,需要在嵌套字段上加 @Valid。

地址對象:

package com.example.order.dto;

import jakarta.validation.constraints.NotBlank;

public record AddressRequest(

        @NotBlank(message = "省份不能為空")
        String province,

        @NotBlank(message = "城市不能為空")
        String city,

        @NotBlank(message = "詳細(xì)地址不能為空")
        String detail
) {
}

訂單對象:

package com.example.order.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record CreateOrderRequest(

        @NotBlank(message = "訂單號不能為空")
        String orderNo,

        @Valid
        @NotNull(message = "收貨地址不能為空")
        AddressRequest address
) {
}

沒有 @Valid 時,只會校驗(yàn) address 是否為 null,不會繼續(xù)校驗(yàn) AddressRequest 里面的 province、citydetail。

集合和泛型元素校驗(yàn)

Jakarta Validation 支持容器元素校驗(yàn)。

比如訂單明細(xì)列表:

package com.example.order.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import java.util.List;

public record SubmitOrderRequest(

        @NotEmpty(message = "訂單明細(xì)不能為空")
        List<@Valid OrderItemRequest> items,

        List<@NotNull(message = "優(yōu)惠券ID不能為空") Long> couponIds
) {
}

明細(xì)對象:

package com.example.order.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public record OrderItemRequest(

        @NotNull(message = "商品ID不能為空")
        Long skuId,

        @NotNull(message = "購買數(shù)量不能為空")
        @Positive(message = "購買數(shù)量需要大于0")
        Integer count
) {
}

幾個細(xì)節(jié):

寫法含義
@NotEmpty List<OrderItemRequest> items列表本身不能為空,且至少有一個元素
List<@Valid OrderItemRequest> items校驗(yàn)列表里每個對象
List<@NotNull Long> couponIds校驗(yàn)列表里每個 ID 不能為 null

Service 方法參數(shù)校驗(yàn)

校驗(yàn)不只發(fā)生在 Controller。

Service 方法參數(shù)也可以校驗(yàn),常見于內(nèi)部服務(wù)、定時任務(wù)、消息消費(fèi)入口。

package com.example.user.service;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Validated
@Service
public class UserQueryService {

    public String getUsername(@Min(value = 1, message = "用戶ID需要大于0") Long userId) {
        return "user-" + userId;
    }

    public void changeNickname(
            @Min(value = 1, message = "用戶ID需要大于0") Long userId,
            @NotBlank(message = "昵稱不能為空") String nickname
    ) {
        // 更新昵稱
    }

    public void create(@Valid CreateUserCommand command) {
        // 創(chuàng)建用戶
    }
}

命令對象:

package com.example.user.service;

import jakarta.validation.constraints.NotBlank;

public record CreateUserCommand(
        @NotBlank(message = "用戶名不能為空")
        String username
) {
}

Service 類上加 @Validated 后,Spring 會通過代理觸發(fā)方法校驗(yàn)。

同一個類內(nèi)部直接調(diào)用本類方法時,可能繞過代理,方法校驗(yàn)不會觸發(fā)。需要把被校驗(yàn)的方法放到另一個 Spring Bean,或者通過代理對象調(diào)用。

返回值校驗(yàn)

方法返回值也可以加約束。

package com.example.user.service;

import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Validated
@Service
public class UserProfileService {

    @NotNull(message = "用戶資料不能為空")
    public UserProfile findProfile(Long userId) {
        return new UserProfile(userId, "Tom");
    }
}

返回對象:

package com.example.user.service;

public record UserProfile(
        Long userId,
        String username
) {
}

返回值校驗(yàn)更適合內(nèi)部服務(wù)契約,不適合濫用。很多業(yè)務(wù)場景下,返回為空本身可能就是合法結(jié)果。

自定義校驗(yàn)注解

內(nèi)置注解覆蓋不了所有業(yè)務(wù)規(guī)則。

比如手機(jī)號格式,可以封裝成一個注解。

先定義注解:

package com.example.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {

    String message() default "手機(jī)號格式不正確";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

再寫校驗(yàn)器:

package com.example.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.regex.Pattern;

public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, 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 || value.isBlank()) {
            return true;
        }
        return PHONE_PATTERN.matcher(value).matches();
    }
}

使用:

package com.example.user.dto;

import com.example.validation.PhoneNumber;
import jakarta.validation.constraints.NotBlank;

public record BindPhoneRequest(

        @NotBlank(message = "手機(jī)號不能為空")
        @PhoneNumber
        String phone
) {
}

自定義校驗(yàn)器里通常把 null 當(dāng)作通過。

原因是是否必填應(yīng)該交給 @NotNull、@NotBlank 這類注解表達(dá)。這樣一個 @PhoneNumber 既可以用于必填手機(jī)號,也可以用于非必填手機(jī)號。

類級別校驗(yàn)

有些規(guī)則需要同時看多個字段。

比如開始時間不能晚于結(jié)束時間。

先定義注解:

package com.example.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {

    String message() default "開始時間不能晚于結(jié)束時間";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

請求對象:

package com.example.activity.dto;

import com.example.validation.ValidDateRange;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

@ValidDateRange
public record ActivityRequest(

        @NotNull(message = "開始時間不能為空")
        @FutureOrPresent(message = "開始時間不能早于當(dāng)前時間")
        LocalDateTime startTime,

        @NotNull(message = "結(jié)束時間不能為空")
        @FutureOrPresent(message = "結(jié)束時間不能早于當(dāng)前時間")
        LocalDateTime endTime
) {
}

校驗(yàn)器:

package com.example.validation;

import com.example.activity.dto.ActivityRequest;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class DateRangeValidator implements ConstraintValidator<ValidDateRange, ActivityRequest> {

    @Override
    public boolean isValid(ActivityRequest value, ConstraintValidatorContext context) {
        if (value == null || value.startTime() == null || value.endTime() == null) {
            return true;
        }
        return !value.startTime().isAfter(value.endTime());
    }
}

類級別校驗(yàn)適合跨字段規(guī)則。單字段規(guī)則仍然放在字段注解上更直觀。

手動校驗(yàn)

脫離 Spring Web 時,也可以手動使用 Validator

package com.example.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

import java.util.Set;

public class ManualValidationDemo {

    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        RegisterCommand command = new RegisterCommand("", "bad-email");

        Set<ConstraintViolation<RegisterCommand>> violations = validator.validate(command);

        for (ConstraintViolation<RegisterCommand> violation : violations) {
            System.out.println(violation.getPropertyPath() + " -> " + violation.getMessage());
        }
    }
}

命令對象:

package com.example.validation;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record RegisterCommand(

        @NotBlank(message = "用戶名不能為空")
        String username,

        @Email(message = "郵箱格式不正確")
        String email
) {
}

輸出大概是:

username -> 用戶名不能為空
email -> 郵箱格式不正確

錯誤消息和國際化

注解里的 message 可以直接寫中文:

@NotBlank(message = "用戶名不能為空")
private String username;

也可以寫消息 key:

@NotBlank(message = "{user.username.notBlank}")
private String username;

然后在 src/main/resources/ValidationMessages.properties 中配置:

user.username.notBlank=用戶名不能為空
user.email.invalid=郵箱格式不正確

Spring Boot 也會結(jié)合應(yīng)用的 MessageSource 解析消息。項(xiàng)目已經(jīng)有 messages.properties、messages_zh_CN.properties 這類國際化文件時,可以把校驗(yàn)文案納入統(tǒng)一管理。

Fail Fast:只返回第一個錯誤

默認(rèn)情況下,一個對象里多個字段不合法,會返回多個錯誤。

有些接口只想返回第一個錯誤,可以開啟 Hibernate Validator 的 fail fast。

Spring Boot 里可以注冊一個配置:

package com.example.config;

import org.hibernate.validator.HibernateValidatorConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ValidationConfig {

    @Bean
    public ValidationConfigurationCustomizer validationConfigurationCustomizer() {
        return configuration -> {
            if (configuration instanceof HibernateValidatorConfiguration hibernateConfiguration) {
                hibernateConfiguration.failFast(true);
            }
        };
    }
}

多錯誤返回適合表單場景,一次性展示所有字段問題。

單錯誤返回適合移動端彈窗、命令式接口、對響應(yīng)體大小比較敏感的場景。

和 JPA 實(shí)體校驗(yàn)的關(guān)系

Jakarta Validation 可以放在 JPA 實(shí)體上。

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;

@Entity
public class UserEntity {

    @Id
    @GeneratedValue
    private Long id;

    @NotBlank(message = "用戶名不能為空")
    private String username;
}

Hibernate ORM 在持久化生命周期中也可以觸發(fā)校驗(yàn)。

不過 Web 接口里更推薦使用專門的請求 DTO:

CreateUserRequest 負(fù)責(zé)接口入?yún)?br />UserEntity 負(fù)責(zé)數(shù)據(jù)庫映射

接口字段和數(shù)據(jù)庫字段經(jīng)常不完全一致。把校驗(yàn)規(guī)則全部堆到實(shí)體上,后期容易被不同接口場景互相影響。

常見問題

引入了注解但校驗(yàn)沒有生效

常見原因:

原因處理方式
缺少 spring-boot-starter-validation添加 validation starter
@RequestBody 前沒有 @Valid 或 @Validated在參數(shù)前添加觸發(fā)注解
普通參數(shù)校驗(yàn)缺少 @Validated在 Controller 或 Service 類上添加 @Validated
Service 內(nèi)部調(diào)用本類方法通過 Spring 代理調(diào)用,或拆到另一個 Bean
嵌套對象缺少 @Valid在嵌套字段或集合元素上添加 @Valid

int 加了 @NotNull 仍然沒效果

int 是基本類型,默認(rèn)值是 0,不會是 null。

如果要表達(dá)必填,使用包裝類型:

@NotNull(message = "年齡不能為空")
private Integer age;

@NotBlank 用在 Integer 上報錯

@NotBlank 只能用于字符串。

數(shù)字必填用 @NotNull,數(shù)值范圍用 @Min、@Max、@Positive 等注解。

@NotNull(message = "數(shù)量不能為空")
@Positive(message = "數(shù)量需要大于0")
private Integer count;

@Valid 和 BindingResult 的位置

Spring MVC 支持在被校驗(yàn)參數(shù)后面緊跟 BindingResult 手動處理錯誤。

@PostMapping
public String create(@Valid @RequestBody CreateUserRequest request,
                     BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return bindingResult.getFieldError().getDefaultMessage();
    }
    return "創(chuàng)建成功";
}

這種寫法適合少量特殊接口。

大多數(shù) REST API 更適合用全局異常處理器統(tǒng)一返回錯誤結(jié)構(gòu)。

實(shí)踐建議

場景建議
Web JSON 入?yún)?/td>DTO 上寫約束,Controller 參數(shù)使用 @Valid
普通參數(shù)Controller 類或 Service 類使用 @Validated
新增和修改規(guī)則差異小使用分組校驗(yàn)
新增和修改字段差異大拆成不同請求 DTO
嵌套對象嵌套字段上加 @Valid
集合元素使用 List<@Valid Item> 或 List<@NotNull Long>
業(yè)務(wù)格式規(guī)則封裝自定義注解
錯誤返回使用 @RestControllerAdvice 統(tǒng)一處理
JPA 實(shí)體避免把所有接口規(guī)則都壓到實(shí)體上

小結(jié)

Jakarta Validation 的核心是聲明式校驗(yàn)。

簡單字段規(guī)則用內(nèi)置注解,跨字段規(guī)則用類級別自定義注解,業(yè)務(wù)格式規(guī)則用自定義約束,接口錯誤返回交給全局異常處理器。

Spring Boot 項(xiàng)目中,常見組合是:

spring-boot-starter-validation
DTO 約束注解
Controller @Valid / @Validated
Service @Validated
@RestControllerAdvice

這樣參數(shù)校驗(yàn)、業(yè)務(wù)邏輯和錯誤返回會分得比較清楚,代碼也更容易維護(hù)。

到此這篇關(guān)于Java Jakarta Validation 實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Java Jakarta Validation 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA和C#的語法特性及優(yōu)缺點(diǎn)對比

    JAVA和C#的語法特性及優(yōu)缺點(diǎn)對比

    Java和C#是當(dāng)今流行的兩種面向?qū)ο蟮木幊陶Z言,它們都源自C語言的語法風(fēng)格,但各自發(fā)展出了獨(dú)特的特性,這篇文章主要介紹了JAVA和C#的語法特性及優(yōu)缺點(diǎn)對比的相關(guān)資料,需要的朋友可以參考下
    2025-11-11
  • visual studio2022 JNI開發(fā)流程的實(shí)現(xiàn)

    visual studio2022 JNI開發(fā)流程的實(shí)現(xiàn)

    本文主要介紹了通過IDEA創(chuàng)建Maven項(xiàng)目并生成JNI頭文件,使用Visual Studio 2022構(gòu)建DLL,最后在Java中加載并調(diào)用C++實(shí)現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2025-07-07
  • Java設(shè)計(jì)模式之動態(tài)代理模式實(shí)例分析

    Java設(shè)計(jì)模式之動態(tài)代理模式實(shí)例分析

    這篇文章主要介紹了Java設(shè)計(jì)模式之動態(tài)代理模式,結(jié)合實(shí)例形式分析了動態(tài)代理模式的概念、功能、組成、定義與使用方法,需要的朋友可以參考下
    2018-04-04
  • springboot使用shiro-整合redis作為緩存的操作

    springboot使用shiro-整合redis作為緩存的操作

    這篇文章主要介紹了springboot使用shiro-整合redis作為緩存的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java集合_淺談Iterable和Iterator的區(qū)別

    java集合_淺談Iterable和Iterator的區(qū)別

    下面小編就為大家?guī)硪黄猨ava集合_淺談Iterable和Iterator的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • Spring Boot如何優(yōu)雅的使用多線程實(shí)例詳解

    Spring Boot如何優(yōu)雅的使用多線程實(shí)例詳解

    這篇文章主要給大家介紹了關(guān)于Spring Boot如何優(yōu)雅的使用多線程的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • java中equals和等號(==)的區(qū)別淺談

    java中equals和等號(==)的區(qū)別淺談

    java中equals和等號(==)的區(qū)別淺談,需要的朋友可以參考一下
    2013-05-05
  • Java數(shù)據(jù)結(jié)構(gòu)二叉樹難點(diǎn)解析

    Java數(shù)據(jù)結(jié)構(gòu)二叉樹難點(diǎn)解析

    樹是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹中稱為結(jié)點(diǎn))按分支關(guān)系組織起來的結(jié)構(gòu),很象自然界中的樹那樣。樹結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會的族譜和各種社會組織機(jī)構(gòu)都可用樹形象表示
    2021-10-10
  • maven profile動態(tài)選擇配置文件詳解

    maven profile動態(tài)選擇配置文件詳解

    這篇文章主要介紹了maven profile動態(tài)選擇配置文件詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • 解決IDEA報錯java無效的目標(biāo)發(fā)行版:22

    解決IDEA報錯java無效的目標(biāo)發(fā)行版:22

    在使用IDEA編譯項(xiàng)目時,可能會遇到JDK版本不一致的錯誤,這篇文章主要介紹了解決IDEA報錯java無效的目標(biāo)發(fā)行版:22的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-10-10

最新評論

阜宁县| 金阳县| 西充县| 伊宁市| 高邮市| 习水县| 白朗县| 柳林县| 富川| 茂名市| 涿州市| 武穴市| 沭阳县| 大城县| 铜鼓县| 铜陵市| 曲麻莱县| 芦溪县| 梅河口市| 怀远县| 芷江| 曲阳县| 广东省| 海伦市| 图木舒克市| 东平县| 榆林市| 林口县| 繁峙县| 五莲县| 平度市| 亚东县| 平谷区| 兴安盟| 观塘区| 井研县| 郧西县| 汕尾市| 榕江县| 福海县| 洛隆县|