이전 포스팅에서 Hibernate Validator
를 통해 어노테이션 기반으로 제약사항을 표현하고 유효성 체크 및 예외처리를 구현하는 방법에 대해 알아보았습니다.
이번 포스팅에서는 사용자 지정(커스텀) 유효성 체크 및 예외처리 방식을 살펴보도록 하겠습니다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class BookingRequestDto {
@NotNull(message = "체크인 날짜를 지정해야 합니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate checkInDate;
@NotNull(message = "체크아웃 날짜를 지정해야 합니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate checkOutDate;
public Booking toEntity(long userId, long roomTypeId) {
return Booking.createInstance(userId, roomTypeId, checkInDate, checkOutDate);
}
}
위 코드는 숙박업소 예약 시 사용하는 BookingRequestDto 입니다.
javax.validation.constraints
패키지에 정의 된 @NotNull
유효성 검증 애노테이션을 사용하였습니다.
@DateTimeFormat 애노테이션은 String 으로 들어오는 날짜 데이터를 LocalDate 타입으로 deserialize 하는 과정에서 생기는 에러를 해결하기 위해 사용하였습니다.
따라서 요청 시 checkOutDate를 지정하지 않으면 400 Status 와 함께 알맞은 예외 메시지를 전달받게 됩니다.
체크아웃 날짜는 체크인 날짜를 앞설 수 없다는 비즈니스 로직이 추가되었을 때, 기존에 존재하는 애노테이션만으로는 해당 데이터의 유효성을 검증할 수 없습니다.
따라서 사용자 지정 커스텀 애노테이션을 통해 유효성을 체크해주어야 합니다.
@Constraint(validatedBy = BookingPeriodValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BookingPeriod {
String message() default "기간이 유효하지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint 애노테이션을 사용하여 필드를 검증할 클래스
를 정의했습니다.
message()는 사용자에게 표시되는 오류 메시지입니다.
public class BookingPeriodValidator implements ConstraintValidator<BookingPeriod, BookingRequestDto> {
@Override
public void initialize(BookingPeriod constraintAnnotation) {
}
@Override
public boolean isValid(BookingRequestDto bookingRequestDto, ConstraintValidatorContext context) {
return bookingRequestDto.isValidPeriod();
}
}
ConstraintValidator
인터페이스를 구현하는 유효성 검사 클래스를 생성합니다.
첫번 째 제네릭 타입은 커스텀 애노테이션, 두번 째 제네릭 타입은 유효성을 검증할 대상이 되는 클래스를 지정합니다.
이 때 isValid 메서드를 구현해야 하는데 해당 메서드의 return 결과가 유효성 검사 통과 유무를 의미하게 됩니다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
@BookingPeriod
public class BookingRequestDto {
@NotNull(message = "체크인 날짜를 지정해야 합니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate checkInDate;
@NotNull(message = "체크아웃 날짜를 지정해야 합니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate checkOutDate;
public Booking toEntity(long userId, long roomTypeId) {
return Booking.createInstance(userId, roomTypeId, checkInDate, checkOutDate);
}
public boolean isValidPeriod() {
if (checkInDate == null || checkOutDate == null) {
return false;
}
return checkOutDate.isAfter(checkInDate);
}
}
Validator
클래스에서는 BookingRequestDto 객체 인스턴스의 isValidPeriod()
메서드의 결과에 따라 유효성 검사 통과 유무를 결정합니다.
따라서 isValidPeriod() 메서드를 적절히 구현합니다.
저는 체크아웃, 체크인 날짜가 존재해야 하며 체크아웃 날짜는 체크인 날짜를 앞설 수 없다는 제약조건을 추가하였습니다.
마지막으로 이전에 생성한 커스텀 애노테이션 @BookingPeriod
을 DTO 클래스 상단에 붙여줍니다.
체크인 날짜가 체크아웃 날짜를 앞서는 경우 위와 같은 에러 메시지를 반환받는 것을 확인할 수 있습니다.