0

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

Java Backend Zero to Hello

📚 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

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í