Java@Data 與 @NotNull 注解沖突問題

第一章:核心概念解析

1.?@Data(Lombok 提供)

  • 自動生成以下方法:
    • getter
    • setter
    • toString()
    • equals()
    • hashCode()
  • 簡化實體類編寫,提高開發效率。

示例:

import lombok.Data;@Data
public class User {private String username;private Integer age;
}

等價于:

public class User {private String username;private Integer age;public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }@Overridepublic String toString() { ... }@Overridepublic boolean equals(Object o) { ... }@Overridepublic int hashCode() { ... }
}

?2.?@NotNull(Java Bean Validation 提供)

  • 表示字段或參數不能為?null
  • 常用于接口參數校驗,通常配合?@Valid?使用。
  • 只在運行時生效(如 Spring MVC 校驗)。

示例:

@PostMapping("/users")
public void createUser(@Valid @RequestBody UserDTO userDTO) {// 如果 userDTO.username == null,會拋出 MethodArgumentNotValidException
}

區別總結

特性@Data@NotNull
來源LombokJava Bean Validation (javax.validation.constraints)
生效階段編譯期運行時
是否阻止 null??(但僅在校驗上下文中)
是否適用于 setter 方法??
是否適用于構造函數??

第二章:常見沖突場景詳解(共 15 個)


場景 1:使用無參構造器創建對象導致字段為 null

問題代碼:

@Data
public class User {@NotNull(message = "用戶名不能為空")private String username;
}User user = new User(); // username == null

解決方案:

方案一:添加有參構造器

@Data
public class User {@NotNull(message = "用戶名不能為空")private String username;public User(String username) {this.username = Objects.requireNonNull(username, "用戶名不能為空");}
}

方案二:使用 Lombok 的?@NonNull

import lombok.NonNull;@Data
public class User {@NonNullprivate String username;
}

@NonNull 是編譯期插入空值檢查,會在生成的 setter 和構造函數中自動加入非空判斷。


場景 2:調用 setter 方法傳入 null 值

問題代碼:

user.setUsername(null); // 不會觸發 @NotNull 校驗

解決方案:

?手動重寫 setter 方法

public class User {@NotNull(message = "用戶名不能為空")private String username;public void setUsername(String username) {this.username = Objects.requireNonNull(username, "用戶名不能為空");}
}

或者使用?@Setter(AccessLevel.NONE)?+ 自定義 setter

import lombok.Data;
import lombok.Setter;@Data
public class User {@Setter(AccessLevel.NONE)@NotNull(message = "用戶名不能為空")private String username;public void setUsername(String username) {this.username = Objects.requireNonNull(username, "用戶名不能為空");}
}

?場景 3:Spring Boot 接口未啟用校驗導致無效約束

問題代碼:

@PostMapping("/users")
public void createUser(@RequestBody UserDTO userDTO) {System.out.println(userDTO.getUsername());
}

即使 username == null,也不會報錯。

解決方案:

啟用?@Valid

@PostMapping("/users")
public void createUser(@Valid @RequestBody UserDTO userDTO) {...
}

?添加全局異常處理器

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic String handleValidationErrors(MethodArgumentNotValidException ex) {return ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));}
}

場景 4:字段類型為基本類型(如 int),無法為 null,但仍被標記為 @NotNull

問題代碼:

@NotNull
private int age;

分析:

  • int?類型不能為?null,所以?@NotNull?沒有意義。
  • 如果數據庫字段允許為?NULL,應使用包裝類型?Integer

正確做法:

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

場景 5:JSON 反序列化時忽略字段非空校驗

問題代碼:

{"username": null
}

反序列化為:

User user = objectMapper.readValue(json, User.class);

不會觸發 @NotNull 校驗。

?解決方案:

  • 在 Controller 中使用?@Valid?觸發校驗;
  • 或者在 DTO 中統一使用?@JsonInclude(Include.NON_NULL)?過濾 null 字段。

?場景 6:構建復雜對象時 Builder 允許字段為 null

問題代碼:

User.builder().age(25).build(); // username == null

解決方案:

重寫 build() 方法進行校驗:

@Builder
public class User {private String username;private Integer age;public static class UserBuilder {public User build() {if (username == null) {throw new IllegalArgumentException("用戶名不能為空");}return new User(this);}}
}

?場景 7:Optional 字段誤加 @NotNull 導致混淆

問題代碼:

@NotNull
private Optional<String> nickname;

分析:

  • Optional?本身就表示“可能為空”,加上?@NotNull?易造成誤解。

正確做法:

private Optional<@NotNull String> nickname; // 表示 Optional 內容必須非空

場景 8:MyBatis Plus 查詢結果返回 null 字段未處理

問題代碼:

User user = userService.getById(1L); // username == null

解決方案:

  • 查詢后手動判斷字段是否為空;
  • 或者使用封裝器統一處理。

?場景 9:前后端交互中字段缺失導致接口失敗

問題 JSON:

{"email": "john@example.com"
}

缺少 username 字段,反序列化為 null,接口執行失敗。

解決方案:

  • 后端使用?@Valid?+?@NotNull?強制字段存在;
  • 前端做好表單必填項控制;
  • 提供清晰的錯誤提示信息。

場景 10:使用 MapStruct 映射實體時忽略字段校驗

問題代碼:

@Mapper
public interface UserMapper {User toEntity(UserDTO dto);
}

解決方案:

  • 在映射后手動校驗;
  • 或者使用?@Valid?包裹整個流程。

場景 11:字段允許為 “空字符串” 但不允許為 null

問題代碼:

@NotNull(message = "昵稱不能為空")
private String nickname;

前端傳了 "",通過校驗,但邏輯上仍需處理。

?正確做法:

使用 @NotBlank 替代:

@NotBlank(message = "昵稱不能為空且不能全為空格")
private String nickname;

場景 12:嵌套對象校驗失效

問題代碼:

public class UserDTO {@NotNullprivate Address address;
}public class Address {@NotNullprivate String street;
}

如果只對 UserDTO 使用 @ValidAddress.street 的校驗不會觸發。

正確做法:

確保使用 @Valid 注解嵌套對象:

public class UserDTO {@Valid@NotNullprivate Address address;
}

場景 13:集合字段校驗失效

問題代碼:

@NotNull
private List<User> users;

傳入空數組 [],不觸發異常。

?正確做法:

使用 @NotEmpty

@NotEmpty(message = "用戶列表不能為空")
private List<User> users;

場景 14:使用?@Validated?實現分組校驗

問題背景:

希望根據不同的業務場景啟用不同的校驗規則。

解決方案:

定義校驗分組:

public interface CreateGroup {}
public interface UpdateGroup {}

使用分組:

public class UserDTO {@NotNull(groups = CreateGroup.class)private String username;@NotNull(groups = UpdateGroup.class)private Long id;
}

Controller 中使用:

@PostMapping("/users")
public void createUser(@Validated(CreateGroup.class) @RequestBody UserDTO userDTO) {...
}

場景 15:自定義校驗注解

問題背景:

希望實現更復雜的校驗邏輯,例如:

  • 用戶名不能以數字開頭
  • 郵箱必須符合企業郵箱格式

?解決方案:

1. 創建自定義注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface ValidUsername {String message() default "用戶名不符合規范";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
2. 實現校驗器
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {return value != null && !Character.isDigit(value.charAt(0));}
}
3. 使用注解
@ValidUsername
private String username;

第三章:最佳實踐總結

場景推薦做法
必須非空字段使用?@NonNull(Lombok)或手動構造器/Setter
接口參數校驗使用?@Valid?+?@NotNull
構建對象使用?@Builder?并重寫?build()?方法
可為空字段使用?Optional<T>?類型
Spring Boot 項目引入?spring-boot-starter-validation
數據庫映射手動判斷字段是否為 null
前后端交互后端強制校驗,前端配合表單驗證
日志輸出使用?@ToString(exclude = {...})?避免敏感字段打印
復雜校驗使用自定義注解或 AOP 實現

第四章:拓展知識點

1.?@NotNull?vs?@NotBlank?vs?@NotEmpty

注解類型是否允許空字符串是否允許空白字符是否允許 null
@NotNull通用???
@NotBlankString???
@NotEmpty集合、數組、Map、String???

2.?@Valid?vs?@Validated

特性@Valid@Validated
支持分組校驗??
支持類級別校驗??
支持 AOP 校驗??
注解位置方法參數上類和方法上均可

3.?Spring Validation 校驗流程圖:

? ? Controller 層 →?@Valid?→ Validator → ConstraintViolationException → 全局異常處理

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86671.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86671.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86671.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

離線部署openstack 2024.1 glance

控制節點鏡像服務 離線下載 apt install --download-only glancemkdir /controller/glance mv /var/cache/apt/archives/*.deb /controller/glance/ dpkg -i /controller/glance/*.deb在一個控制節點操作 CREATE DATABASE glance; GRANT ALL PRIVILEGES ON glance.* TO glan…

.NET AOT 詳解

簡介 AOT&#xff08;Ahead-Of-Time Compilation&#xff09;是一種將代碼直接編譯為機器碼的技術&#xff0c;與傳統的 JIT&#xff08;Just-In-Time Compilation&#xff09;編譯方式形成對比。在.NET 中&#xff0c;AOT 編譯可以在應用發布時將 IL&#xff08;中間語言&…

博客系統自動化測試

基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架構建的個人博客系統&#xff0c;通過分層架構實現高效協作&#xff1a;Spring負責依賴注入與事務管理&#xff0c;Spring MVC處理HTTP請求分發&#xff0c;MyBatis完成數據持久化操作。系統包含以下核心功能模塊…

animate.css詳解:輕松實現網頁動畫效果

前言 在網頁設計中&#xff0c;動畫效果不僅僅是視覺上的裝飾&#xff0c;更是提升用戶體驗的重要元素。animate.css 作為一個輕量級的 CSS 動畫庫&#xff0c;提供了豐富的預設動畫效果&#xff0c;本文將探討 animate.css 使用方法以及在實際項目中的應用案例&#xff0c;幫助…

【多智能體】基于嵌套進化算法的多代理工作流

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一個正在變禿、變強的文藝傾年。 &#x1f514;本專欄《人工智能》旨在記錄最新的科研前沿&#xff0c;包括大模型、具身智能、智能體等相關領域&#xff0c;期待與你一同探索、學習、進步&#xff0c;一起卷起來叭&#xff…

電源知多少?LDO VS DCDC((下)

首先補充幾個上一節沒有提到的知識&#xff0c;我們通常說的DCDC同步整流是指什么&#xff1f; 同步是指采用通態電阻極低的專用功率MOS來取代整流二極管以降低整流損耗&#xff0c;&#xff0c;但是同步整流有以下兩點需要注意&#xff1a;1、MOS在導通之后的壓降比較低&…

數組方法_push()/pop()/數組方法_shift()/unshift()

push 方法用于在數組的末端添加一個或多個元素&#xff0c;并返回添加新元 素后的數組長度。注意&#xff0c;該方法會改變原數組 var arr [];arr.push("顫三") // 1arr.push(itbaizhan) // 2arr.push(true, {}) // 4arr // [顫三 , itbaizhan, true, {}] pop 方法用…

腦機新手指南(八):OpenBCI_GUI:從環境搭建到數據可視化(下)

一、數據處理與分析實戰 &#xff08;一&#xff09;實時濾波與參數調整 基礎濾波操作 60Hz 工頻濾波&#xff1a;勾選界面右側 “60Hz” 復選框&#xff0c;可有效抑制電網干擾&#xff08;適用于北美地區&#xff0c;歐洲用戶可調整為 50Hz&#xff09;。 平滑處理&…

多頭與空頭:市場博弈的兩面

在金融市場中&#xff0c;多頭&#xff08;Bull&#xff09;和空頭&#xff08;Bear&#xff09;代表兩種截然相反的投資策略&#xff0c;它們的博弈構成了市場價格波動的核心動力。 1. 概念對比&#xff1a;看漲與看跌的本質區別 多頭&#xff08;Bull&#xff09;&#xff0…

Excel 發現此工作表中有一處或多處公式引用錯誤。請檢查公式中的單元格引用、區域名稱、已定義名稱以及到其他工作簿的鏈接是否均正確無誤。彈窗

Excel 提示“發現此工作表中有一處或多處公式引用錯誤”通常表示公式中存在無效引用。以下是系統化的檢查步驟&#xff0c;幫助你定位和修復問題&#xff1a; 1. 檢查單元格引用&#xff1a; 無效單元格引用&#xff1a;檢查公式中的單元格地址&#xff08;如 A1、B10&…

變量 varablie 聲明- Rust 變量 let mut 聲明與 C/C++ 變量聲明對比分析

一、變量聲明設計&#xff1a;let 與 mut 的哲學解析 Rust 采用 let 聲明變量并通過 mut 顯式標記可變性&#xff0c;這種設計體現了語言的核心哲學。以下是深度解析&#xff1a; 1.1 設計理念剖析 安全優先原則&#xff1a;默認不可變強制開發者明確聲明意圖 let x 5; …

【指針】(適合考研、專升本)

指針 &與*是兩個作用相反的運算符。 二級指針只能保存一級指針變量的地址和指向指針數組&#xff0c;其余情況不考慮。 int *p[2];int a12;int b15;*p&a;*(p1)&b;printf("%d\n%d\n",**p,**(p1));int **rp;printf("%d\n",**r); 普遍變量…

電路圖識圖基礎知識-行程開關自動往返運行控制電路詳解(二十三)

行程開關自動往返運行控制電路詳解 在機床設備運行中&#xff0c;部分工作臺需在特定距離內自動往復循環&#xff0c;行程開關自動往返運行控制電路可實現該功能&#xff0c;通過行程開關自動控制電動機正反轉&#xff0c;保障工作臺有序運動&#xff0c;以下展開詳細解析。 …

SpringBoot學習day1-SpringBoot的簡介與搭建

springboot回顧springspringbootspringboot搭建&#xff08;新聞為例&#xff09;springboot中的配置文件spring集成jdbc,mybatis,阿里巴巴數據源**SpringBoot 集成日志功能**(了解)常用日志組件日志級別 springboot統一異常處理 springboot 回顧spring spring是一個輕量級的…

【牛客小白月賽117】E題——種類數小結

1 初步想法 1.1 前置知識&#xff1a;vector數組的去重操作 unique()將不重復的元素放在數組前面&#xff0c;重復元素移到后面&#xff0c;qs獲取不重復元素的后一個位置&#xff0c;之后用erase()函數去除重復元素。 qsunique(a.begin()1,a.begin()k1); a.erase(qs,a.end(…

linux之kylin系統nginx的安裝

一、nginx的作用 1.可做高性能的web服務器 直接處理靜態資源&#xff08;HTML/CSS/圖片等&#xff09;&#xff0c;響應速度遠超傳統服務器類似apache支持高并發連接 2.反向代理服務器 隱藏后端服務器IP地址&#xff0c;提高安全性 3.負載均衡服務器 支持多種策略分發流量…

MatAnyone本地部署,視頻分割處理,綠幕摳像(WIN/MAC)

大家好&#xff0c;今天要和大家分享的項目是MatAnyone&#xff0c;與上一篇分享的SAM2LONG類似&#xff0c;不過上次的分享沒有提到如何在 MAC 上部署&#xff0c;后來有小伙伴私信說希望能出一個 MAC 版本的。那正好看到MatAnyone這個項目順手就寫下來。該項目基于SAM2同樣可…

記錄下blog的成長過程

2025-06-11 新人榜83 2025-06-09 新人榜87 北京市原力月榜 80

C語言學習20250611

指針 指針類型 int p;》普通的整形變量int *p;》p先與*結合&#xff0c;表示p為指針&#xff0c;該指針指向的內容的數據類型為整型int p[3];》p為一個由整型數據組成的數組int *p[3];》因為[]比*優先級高&#xff0c;p先與方括號結合&#xff0c;所以p為一個數組&#xff0c…

【AI智能體】Dify 從部署到使用操作詳解

目錄 一、前言 二、Dify 介紹 2.1 Dify 是什么 2.2 Dify 核心特性 2.2.1 多模型支持 2.2.2 可視化編排工作流 2.2.3 低代碼/無代碼開發 2.3 Dify 適用場景 2.4 Dify 與Coze的對比 2.4.1 定位與目標用戶 2.4.2 核心功能對比 2.4.3 開發體驗與成本 2.4.4 適用場景對比…