[우아한테크코스] 7월 10일 TIL

2 minute read

[Java] Builder 패턴

빌더패턴이란, 객체 생성을 깔끔하고 유연하게 하기 위한 기법이다.
필수 인자를 받는 생성자를 만들고, 선택 인자들을 점진적으로 체이닝하여 받을 수 있도록 한다.

객체 내에 Builder 클래스를 만들고 선택 인자들을 부생성자로 추가해둔다.

최종 객체 생성은 다음과 같이 할 수 있다.

Reservation reservation = new Reservation
    .Builder(123, 345)
    .date(678)
    .description(890)
    .build();

이와 같이 구현함으로써 각 인자가 어떤 의미인지 알기 쉽고 immutable한 객체로 만들 수 있다.

Lombok을 사용할 경우 더욱 간단히 빌더패턴을 적용할 수 있다.
@Builder 어노테이션을 적용하면 아래와 같이 선언만 해도 빌더를 사용할 수 있다.

선언부

@Builder
public class Reservation {
    private final int name;
    private final int date;
    private final int description;
}

구현부

Reservation.ReservationBuilder builder = Reservation.builder();
builder.name(345);
builder.date(678);
builder.description(123);
Reservation reservation = builder.build();

Reservation reservation = Reservation.builder()
    .name(123)
    .date(456)
    .description(789)
    .build();

[Spring] custom validator 만들기

ConsistentDate 어노테이션

@Constraint(validatedBy = ConsistentDateValidator.class)
@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Documented
public @interface ConsistentDate {

    String message() default
            "종료 시간을 확인해주세요.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

//    String startTime();
//
//    String endTime();
//
//    @Target({ElementType.TYPE})
//    @Retention(RetentionPolicy.RUNTIME)
//    @interface List {
//        Constraint[] value();
//    }
}

ConsistentDateValidator 구현부

public class ConsistentDateValidator
        implements ConstraintValidator<ConsistentDate, Object[]> {
//    private LocalDateTime startTime;
//    private LocalDateTime endTime;
//
//    @Override
//    public void initialize(ConsistentDate constraintAnnotation) {
//        this.startTime = LocalDateTime.parse(constraintAnnotation.startTime(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
//        this.endTime =  LocalDateTime.parse(constraintAnnotation.endTime(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
//    }
//
//    @Override
//    public boolean isValid(Object value, ConstraintValidatorContext context) {
//        if(startTime.isBefore(LocalDateTime.now())) {
//            throw new ImpossibleStartTimeException();
//        }
//
//        return startTime.isBefore(endTime);
//    }

    @Override
    public boolean isValid(
            Object[] value,
            ConstraintValidatorContext context) {

        if (value[0] == null || value[1] == null) {
            return true;
        }

        if (((LocalDateTime) value[0]).isBefore(LocalDateTime.now())) {
            throw new ImpossibleStartTimeException();
        }

        return ((LocalDateTime) value[0]).isAfter(LocalDateTime.now())
                && ((LocalDateTime) value[0]).isBefore((LocalDateTime) value[1]);
    }
}

등록한 타입에 맞춰서 어노테이션 달아서 적용

String 타입으로 어노테이션에 넣은 경우는 @ConsistentDate(startTime = "startDateTime", endTime = "endDateTime"), 그냥 메서드 위에 단 경우에는 @ConsistentDate로 적용

[JPA] and/or combination

필요한 것은 spaceId와 매칭되면서 startTime과 endTime 사이에도 매칭되는 것이었다. 기호로는 (spaceId && StartTimeBetween) || (spaceId &&EndTimeBetween) 이다.
이를 위해서 existBySpaceIdAndStartTimeBetweenOrEndTimeBetween을 하였다.
하지만 이렇게 되면 (spaceId && StartTimeBetween)||EndTimeBetween 을 보는 것이었다.
검색을 해봤지만 QueryDsl을 이용하는 방안밖에 찾지 못했고 결국에 jpa 메서드를 두번 날리고 그 둘의 결과를 OR 연산 하는 방안으로 수정했다.
existsBySpaceIdAndStartTimeBetween || existsBySpaceIdAndEndTimeBetween

그리고 나는 처음에는 내가 가진 start와 end 사이에 DB에 존재하는 start나 end가 있으면 될 것이라고 생각했는데 JPA의 Between 쿼리문은 내가 가진 start와 end 사이에 DB에 존재하는 start가 있는지 보고, end가 있는지 보는 일이 따로 일어나야 했다. 그래서 하나의 between ? 쿼리문마다 두개의 인자를 전달해주어야 한다.