[Java Backend Zero to Hello] BÀI 5.7: VALIDATION
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 5: Spring Framework & Spring Boot | Bài 53/86
BÀI 5.7: VALIDATION
Mục tiêu
- Sử dụng Bean Validation
- Các annotation validation phổ biến
- Custom validator
- Validation trong Service layer
1. DEPENDENCY
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. CÁC ANNOTATION PHỔ BIẾN
2.1 Null & Empty
| Annotation | Mô tả |
|---|---|
@NotNull |
Không được null |
@NotEmpty |
Không null và không rỗng (cho Collection, String, Array) |
@NotBlank |
Không null và không chỉ chứa whitespace (cho String) |
2.2 Size & Length
| Annotation | Mô tả |
|---|---|
@Size(min, max) |
Độ dài (String, Collection, Array) |
@Length(min, max) |
Độ dài String (Hibernate) |
2.3 Number
| Annotation | Mô tả |
|---|---|
@Min(value) |
>= value |
@Max(value) |
<= value |
@DecimalMin(value) |
>= value (BigDecimal) |
@DecimalMax(value) |
<= value |
@Positive |
> 0 |
@PositiveOrZero |
>= 0 |
@Negative |
< 0 |
@NegativeOrZero |
<= 0 |
@Digits(integer, fraction) |
Số chữ số |
2.4 String
| Annotation | Mô tả |
|---|---|
@Pattern(regexp) |
Match regex |
@Email |
Email hợp lệ |
2.5 Date & Time
| Annotation | Mô tả |
|---|---|
@Past |
Ngày trong quá khứ |
@PastOrPresent |
Quá khứ hoặc hiện tại |
@Future |
Ngày trong tương lai |
@FutureOrPresent |
Tương lai hoặc hiện tại |
2.6 Boolean
| Annotation | Mô tả |
|---|---|
@AssertTrue |
Phải true |
@AssertFalse |
Phải false |
3. SỬ DỤNG TRONG DTO
public record CreateUserRequest(
@NotBlank(message = "Tên không được rỗng")
@Size(min = 2, max = 100, message = "Tên từ 2-100 ký tự")
String name,
@NotNull(message = "Email không được null")
@Email(message = "Email không hợp lệ")
String email,
@NotNull
@Min(value = 0, message = "Tuổi phải >= 0")
@Max(value = 150, message = "Tuổi phải <= 150")
Integer age,
@Pattern(regexp = "^\\d{10}$", message = "SĐT phải 10 chữ số")
String phone,
@Past(message = "Ngày sinh phải trong quá khứ")
LocalDate birthDate
) {}
4. SỬ DỤNG TRONG CONTROLLER
@PostMapping
public ResponseEntity<UserResponse> create(
@RequestBody @Valid CreateUserRequest request) {
UserResponse user = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
5. XỬ LÝ LỖI VALIDATION
5.1 MethodArgumentNotValidException
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(fe -> errors.put(fe.getField(), fe.getDefaultMessage()));
ErrorResponse error = new ErrorResponse(
400, "Validation failed", errors, LocalDateTime.now()
);
return ResponseEntity.badRequest().body(error);
}
5.2 ConstraintViolationException (cho @PathVariable, @RequestParam)
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraint(
ConstraintViolationException ex) {
Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
Map<String, String> errors = new HashMap<>();
for (ConstraintViolation<?> v : violations) {
errors.put(v.getPropertyPath().toString(), v.getMessage());
}
return ResponseEntity.badRequest().body(
new ErrorResponse(400, "Validation failed", errors, LocalDateTime.now())
);
}
6. CUSTOM VALIDATOR
6.1 Annotation
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AdultValidator.class)
public @interface Adult {
String message() default "Phải >= 18 tuổi";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
6.2 Validator
public class AdultValidator implements ConstraintValidator<Adult, Integer> {
@Override
public boolean isValid(Integer age, ConstraintValidatorContext context) {
return age != null && age >= 18;
}
}
6.3 Sử dụng
public record CreateUserRequest(
@Adult Integer age
) {}
7. CLASS-LEVEL VALIDATION
@PasswordMatch // Custom annotation
public record ChangePasswordRequest(
@NotBlank String oldPassword,
@NotBlank @Size(min = 8) String newPassword,
@NotBlank String confirmPassword
) {}
@Constraint(validatedBy = PasswordMatchValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordMatch {
String message() default "Mật khẩu không khớp";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PasswordMatchValidator
implements ConstraintValidator<PasswordMatch, ChangePasswordRequest> {
@Override
public boolean isValid(ChangePasswordRequest req,
ConstraintValidatorContext context) {
return req.newPassword().equals(req.confirmPassword());
}
}
8. VALIDATION GROUPS
public interface OnCreate {}
public interface OnUpdate {}
public class UserDTO {
@Null(groups = OnCreate.class)
@NotNull(groups = OnUpdate.class)
private Long id;
@NotBlank(groups = {OnCreate.class, OnUpdate.class})
private String name;
}
// Controller
@PostMapping
public UserDTO create(@RequestBody @Validated(OnCreate.class) UserDTO dto) { ... }
@PutMapping("/{id}")
public UserDTO update(@PathVariable Long id,
@RequestBody @Validated(OnUpdate.class) UserDTO dto) { ... }
9. VALIDATION TRONG SERVICE
@Service
public class UserService {
public User create(CreateUserRequest request) {
// Validation logic
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateException("Email đã tồn tại");
}
// ...
}
}
Hoặc dùng @Valid trên method parameter:
public User create(@Valid CreateUserRequest request) { ... }
10. PROGRAMMATIC VALIDATION
@Service
@RequiredArgsConstructor
public class UserService {
private final Validator validator;
public void validate(CreateUserRequest request) {
Set<ConstraintViolation<CreateUserRequest>> violations =
validator.validate(request);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
11. BÀI TẬP THỰC HÀNH
Bài 1: User Validation
Validate CreateUserRequest với đầy đủ các annotation.
Bài 2: Custom Validator
Tạo @StrongPassword yêu cầu:
- Ít nhất 8 ký tự
- Có chữ hoa, chữ thường
- Có số
- Có ký tự đặc biệt
Bài 3: Class-level Validation
Tạo @FieldMatch so sánh 2 field.
12. TÓM TẮT
| Annotation | Mô tả |
|---|---|
@NotNull, @NotEmpty, @NotBlank |
Null/Empty check |
@Size, @Length |
Độ dài |
@Min, @Max, @Positive |
Số |
@Pattern, @Email |
String |
@Past, @Future |
Ngày |
@Valid |
Kích hoạt validation |
| Custom Validator | Logic riêng |
| Validation Groups | Theo ngữ cảnh |
Bài tiếp theo: 5.8 Exception Handling
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 5.6: SPRING BOOT REST API
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 5.8: EXCEPTION HANDLING
All rights reserved