프로젝트에서 헤어샵과 날짜별로 예약 가능 시간들을 포함한 디자이너 리스트를 출력하는 기능이 필요했다.
디자이너와 예약이 일 대 다 관계이고 lazy fetch type을 사용하였으며, request와 response는 다음과 같다.
@Getter
@AllArgsConstructor
@Builder
public class ReservationTimeRequestDto {
@NotNull(message = "날짜를 입력해주세요.")
private LocalDate date;
@NotBlank(message = "예약 시작 시간을 입력해주세요.")
@Pattern(regexp = "^([01][0-9]|2[0-3]):([0-5][0-9])$", message = "예약 시작 시간을 HH:mm 으로 입력해주세요.")
private String reservationStartTime;
@NotBlank(message = "예약 마감 시간을 입력해주세요.")
@Pattern(regexp = "^([01][0-9]|2[0-3]):([0-5][0-9])$", message = "예약 마감 시간을 HH:mm 으로 입력해주세요.")
private String reservationEndTime;
}
@AllArgsConstructor
@Builder
@Getter
public class ReservationTimeResponseDto {
private Long designerId;
private Position designerPosition;
private String designerName;
private String designerImage;
private String designerInstruction;
private List<String> reservationTimes;
}
일반적인 방법으로 디자이너를 조회해서 해당 디자이너가 가지고 있는 예약들을 가져온다면 다음과 같은 이슈가 발생한다.
lazy fetch type에서 조회를 수행하면 조회의 주체가 되는 엔터티만 영속성 컨테이너에 저장되며, 일 대 다 연관관계인 엔터티들은 나중에
get()
을 수행할 때 마침내 영속성 컨테이너에 저장된다. 따라서 N 번의 추가 쿼리가 필요하다. eager fetch type은 조회를 수행하면 일 대 다 연관 관계 모두 영속성 컨테이너에 저장되어 한 번의 쿼리로 수행되는 것 같아 보이지만 실제로는 추가적으로 N개의 쿼리가 날라가는 N+1 문제가 발생한다.
이는 대량의 추가적인 쿼리를 요구하므로 성능 문제와 직결 된다.
따라서 한 번의 쿼리로 연관 관계 모두 영속성 컨테이너에 저장하는 방법이 필요했으며 fetch join이 그 방법이었다.
추가적인 쿼리 없이 한 번의 쿼리로 inner join을 수행하여 연관 관계 모두 영속성 컨테이너에 저장하는 쿼리이다.
문법은 다음과 같다.
select t from Team t join fetch t.members
하지만 fetch join도 카테시안 곱이 발생할 수 있기 때문에, distinct를 사용하여 중복을 제거해야 한다.
또한 inner join이기 때문에 예시에서 member가 존재하지 않으면 team도 조회되지 않는다.