메인 프로젝트 진행하던 도중 고객이 선택한 기간에 예약이 다 찼는지 확인하는 로직을 짜야 했었다.
처음엔 단순히 기간에 해당하는 예약들을 찾아 예약된 강아지의 수들을 합치기만 하면 된다는 생각으로 jqpl을 이용하여 강아지 수의 합을 구했다.
@Query("select COALESCE(sum(r.dogCount),0) from Reservation r where r.companyId = :companyId and " +
"((:checkIn < r.checkIn and r.checkIn < :checkOut) or " +
"(:checkIn between r.checkIn and r.checkOut) or " +
"(:checkIn < r.checkIn and r.checkIn < :checkOut))") //COALESCE -> 값이 없을 경우 두번째 파라미터를 리턴한다고 합니다...
Integer findByCheckInCheckOut(LocalDate checkIn, LocalDate checkOut, Long companyId);
위의 쿼리문으로 기간 내에 예약된 강아지의 수와 호텔의 총 강아지 수용 가능 마리 수를 비교하는 방식으로 로직을 짰으나 모든 예약된 강아지의 마리 수를 count하면 기존의 날짜가 다른 예약된 강아지들 까지도 계산이 된다는 사실을 놓치고 말았다.
결국 위의 @Query는 지우고 다른 방식을 찾아보기로 하였다.
어떻게 해야 할지 고민하던 도중 팀원 중 한 분이 아이디어를 냈다.
reservation 엔티티에 1 : N 관계의 엔티티를 만든 다음 checkInDate ~ checkOutDate 사이의 모든 날짜별로 엔티티를 저장하는 방식으로 진행하자는 아이디어였다.
위의 방법대로 하면 기간 사이에 위치한 날짜들 중 예약된 강아지들의 마리 수가 가장 큰 값을 구하기가 쉬워져서 처음은 위의 방식을 채택 하였다.
하지만 계산 하나만을 위해 엔티티를 많이 만들어 저장한다는 점이 문제였다.
먼저 고객이 선택한 날짜 사이에 기존의 예약들을 조회하였다.
// 고객이 선택한 호텔 중 고객의 체크아웃 날짜보다 체크인할 예약 && 예약이 확정된 예약들 조회
@Query("select " +
" r from Reservation r " +
" where r.companyId = :companyId and r.checkInDate >= :checkOut and r.status = confirmed")
List<Reservation> findByCheckInCheckOut(Long companyId, LocalDateTime checkOut);
그 다음 날짜별로 예약된 강아지의 총 합을 구한 다음 Map에 저장하였고 날짜들 중 가장 예약이 많은 날의 강아지 마리 수를 구하여 호텔의 최대 수용 가능 마리 수와 비교하는 방식으로 로직을 짰다.
public Integer occupiedRoomCount(List<Reservation>reservations,
LocalDate checkInDate,
LocalDate checkOutDate) {
Map<LocalDate, Integer> occupiedMap = getOccupiedMap(reservations, checkInDate, checkOutDate);
Set<LocalDate> dateSet = occupiedMap.keySet();
Integer max = 0;
for (LocalDate localDate : dateSet) {
Integer bookedQuantity = occupiedMap.get(localDate);
if(max <= bookedQuantity) {
max = bookedQuantity;
}
}
return max;
}
//선택한 날짜 사이에 일별마다 예약된 강아지수
public Map<LocalDate, Integer> getOccupiedMap(List<Reservation>reservations,
LocalDate checkInDate,
LocalDate checkOutDate) {
Map<LocalDate, Integer> occupiedMap = new HashMap<>();
for(LocalDate date = checkInDate; date.isBefore(checkOutDate); date = date.plusDays(1)) {
LocalDate currentDate = date;
Integer occupied = reservations.stream()
.filter(reservation -> isBetween(currentDate, reservation))
.mapToInt(ReservationList::getDogCount).sum();
occupiedMap.put(currentDate, occupied);
}
return occupiedMap;
}
public boolean isBetween(LocalDate date, Reservation reservation) {
LocalDate checkInDate = reservation.getCheckInDate();
LocalDate checkOutDate = reservation.getCheckOutDate();
return date.isBefore(checkOutDate) && date.isAfter(checkInDate);
}
위와 같이 로직을 짠 결과 1 : N 연관관계 엔티티를 만들 때 보다 성능 개선을 할 수 있었고 DB 공간 또한 절약할 수 있게 되었다.
개편 이전
개편 이후