0

[Java Backend Zero to Hello] [Phase 5] BÀI 5.7: VALIDATION

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 5: Spring Framework & Spring Boot 📖 Nội dung: BÀI 5.7: VALIDATION 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí