第一章:核心概念解析
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 |
---|---|---|
來源 | Lombok | Java 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
使用 @Valid
,Address.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 | 通用 | ? | ? | ? |
@NotBlank | String | ? | ? | ? |
@NotEmpty | 集合、數組、Map、String | ? | ? | ? |
2.?@Valid
?vs?@Validated
特性 | @Valid | @Validated |
---|---|---|
支持分組校驗 | ? | ? |
支持類級別校驗 | ? | ? |
支持 AOP 校驗 | ? | ? |
注解位置 | 方法參數上 | 類和方法上均可 |
3.?Spring Validation 校驗流程圖:
? ? Controller 層 →?@Valid
?→ Validator → ConstraintViolationException → 全局異常處理