99 Ngày Spring — Ngày 67: Custom validation
@NotNull và @Size xử lý nhiều quy tắc đơn giản. Khi điều kiện phụ thuộc hai field hoặc quy tắc miền, hãy viết constraint riêng để tái sử dụng và kiểm thử. Trong Spring Boot 3.x, Bean Validation dùng namespace jakarta.validation; controller cần @Valid hoặc @Validated ở điểm nhận dữ liệu.

1. Khai báo constraint
Annotation có @Constraint(validatedBy=...), @Target và @Retention(RUNTIME). Giữ message, groups và payload theo hợp đồng Bean Validation. Đặt annotation ở TYPE nếu cần xem nhiều field của cùng DTO; không ép một validator cấp field đọc ngầm field khác.
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {
String message() default "{booking.dateRange.invalid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}2. Kiểm tra liên trường
ConstraintValidator<A,T> nhận annotation và giá trị DTO. Với khoảng ngày, hãy tách việc null cho @NotNull ở từng field, rồi chỉ so sánh khi cả hai có giá trị. Đưa lỗi về endDate để client biết sửa đâu; sau khi tắt default violation, phải tạo ít nhất một violation mới khi trả false.
public boolean isValid(Booking b, ConstraintValidatorContext c) {
if (b == null || b.startDate() == null || b.endDate() == null)
return true;
if (b.endDate().isAfter(b.startDate())) return true;
c.disableDefaultConstraintViolation();
c.buildConstraintViolationWithTemplate("{booking.dateRange.invalid}")
.addPropertyNode("endDate").addConstraintViolation();
return false;
}3. Kích hoạt tại API
Đặt @Valid trên @RequestBody để Spring MVC kiểm tra DTO. Lỗi validation mặc định thành phản hồi 400; khi chuẩn hóa lỗi, giữ field và mã thông điệp ổn định cho client. Không dùng validation như biện pháp duy nhất bảo vệ invariant trong database: ghi từ nguồn khác vẫn có thể bỏ qua controller.
@ValidDateRange
record Booking(@NotNull LocalDate startDate,
@NotNull LocalDate endDate) {}
@PostMapping("/bookings")
ResponseEntity<?> create(@Valid @RequestBody Booking body) {
return service.create(body);
}4. Test cả biên và thông điệp
Thử ngày kết thúc bằng ngày bắt đầu, trước ngày bắt đầu, null và khoảng hợp lệ. Dùng Validator để unit test constraint, rồi gửi HTTP thật để xác nhận @Valid và dạng lỗi. Nếu rule cần truy vấn database, xử lý race condition bằng unique constraint hoặc transaction; validator chỉ cho phản hồi sớm.
Thông điệp nên là key trong messages.properties để ngày 68 dịch được.
Không đưa dữ liệu nhạy cảm vào nội dung lỗi.
Constraint tốt biểu diễn một quy tắc rõ ràng, trả lỗi tại đúng chỗ và được kiểm thử qua HTTP. Ngày 68 sẽ dịch các thông điệp ấy theo locale.
Tài liệu đối chiếu
https://docs.spring.io/spring-framework/reference/core/validation/beanvalidation.html
https://jakarta.ee/specifications/bean-validation/3.0/jakarta-bean-validation-spec-3.0.html
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


