7.상영시간가져오기

강재영's avatar
Dec 16, 2024
7.상영시간가져오기
 

1. 관리자페이지에서 상영시간을 등록하기

 
 
12일을 첫날로 잡고
페이지를 호출하면 12일에 관한 상영관을 업로드 시켰다.

controller

@GetMapping("/admin/showtime") public String showtime(HttpServletRequest request, Model model){ Admin sessionAdmin = (Admin) session.getAttribute("sessionAdmin"); int cinemaId = 1; LocalDate startDate = LocalDate.of(2024, 9, 12); LocalDate today = startDate; // 12일을 "오늘"로 취급 List<Map<String, Object>> dateList = new ArrayList<>(); for (int i = 0; i < 7; i++) { LocalDate currentDate = startDate.plusDays(i); Map<String, Object> dateMap = new HashMap<>(); dateMap.put("formattedDate", currentDate.format(DateTimeFormatter.ofPattern("dd"))); dateMap.put("formattedDay", currentDate.format(DateTimeFormatter.ofPattern("E", Locale.KOREAN))); dateMap.put("isToday", currentDate.equals(today)); // 12일을 기준으로 "오늘" 처리 dateList.add(dateMap); } AdminResponse.CinemaWithScreensDTO cinemaSchedule = adminService.상영관별상영스케줄(sessionAdmin, startDate); model.addAttribute("dates", dateList); // 생성된 날짜 리스트를 모델로 추가 request.setAttribute("model",cinemaSchedule); return "admin/showtime"; }
mutach 파일에서 날짜에 관한 내용을 사용하기위하여 Map을 사용하여 formattedDate, formattedDay, isToday를 추가하였다.
 

service

public AdminResponse.CinemaWithScreensDTO 상영관별상영스케줄(Admin sessionAdmin, LocalDate selectedDate) { Long cinemaId= 1L; Cinema cinema = cinemaRepository.findById(cinemaId) .orElseThrow(() -> new IllegalArgumentException("Cinema not found")); // DTO 생성 (Cinema 정보만 포함) AdminResponse.CinemaWithScreensDTO cinemaWithScreensDTO = new AdminResponse.CinemaWithScreensDTO(cinema); // 2. Screen 정보 조회 List<Long> screenIds = new ArrayList<>(); for (Screen screen : cinema.getScreens()) { screenIds.add(screen.getId()); System.out.println("----------------"); System.out.println(screen.getId()); } // 3. 상영관과 상영시간을 fetch join으로 한 번에 조회 + 시간을 가져오기 List<Screen> screensWithShowtimesAndDate = screenRepository.findScreensWithShowtimesByIdsAndDate(screenIds, selectedDate); //TODO 정리하기 시간을 가지고와서 밑에거는 안씀 // 3. 상영관과 상영시간을 fetch join으로 한 번에 조회 List<Screen> screensWithShowtimes = screenRepository.findScreensWithShowtimesByIds(screenIds); // 4. 상영관과 상영시간을 DTO에 추가 for (Screen screen : screensWithShowtimesAndDate) { System.out.println(screen.getId()); AdminResponse.ScreenDTO screenDTO = new AdminResponse.ScreenDTO(screen); cinemaWithScreensDTO.addScreen(screen); // Screen 추가 } return cinemaWithScreensDTO; }
우선 영화관을 조회하는 로직인데 우선 1번 영화관을 고정하여 가지고왔다.
영화관 1번에 관한 내용을 db에서 받아와고 이 영화관에 ontwomany로 등록되어있는 screen의 id를 list로 가지고와 in절을 사용하여 상영시간에 관한 db를 가지고왔다.
이때 12일이면 12일에 관한 정보만을 가지고와야하니 파라미터로 LocalDate selectedDate를 받아 넘겨주었다.
 

repository

public interface ScreenRepository extends JpaRepository<Screen, Long> { //TODO MYSQL로 바꾸면 바꿔야함 /* * MySQL은 DATE() 함수를 지원하지만, H2나 다른 데이터베이스에서는 이를 지원하지 않을 수 있습니다. 대안으로 CAST나 TRUNC 함수를 사용하여 시간 부분을 제거하고 날짜만 비교할 수 있습니다. * 여기서는 H2 데이터베이스에서 사용할 수 있는 CAST를 이용한 방법*/ @Query("SELECT s FROM Screen s LEFT JOIN FETCH s.showtimes st LEFT JOIN FETCH st.movie " + "WHERE s.id IN :screenIds AND (st.startedAt IS NULL OR CAST(st.startedAt AS date) = :selectedDate)") List<Screen> findScreensWithShowtimesByIdsAndDate(@Param("screenIds") List<Long> screenIds, @Param("selectedDate") LocalDate selectedDate);
파라미터로 가져온 상영관id와 참고하는 날짜를 사용하여 where문을 만들었고
상영시간이 없어도 상영관이 등록이 되어있다면 나와야하는 페이지이므로 LEFT JOIN FETCH를 사용하여 상영시간이 없어도 상영관을 가지고 오게하였다.
 

ResponseDTO

package shop.mtcoding.filmtalk.admin; import lombok.Data; import shop.mtcoding.filmtalk.cinema.Cinema; import shop.mtcoding.filmtalk.movie.Movie; import shop.mtcoding.filmtalk.screen.Screen; import shop.mtcoding.filmtalk.showtime.Showtime; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Data public class AdminResponse { @Data public static class movieDTO{ private Long movieId; private String movieName; public movieDTO(Movie movie) { this.movieId = movie.getId(); this.movieName = movie.getMovieNm(); } } @Data public static class CinemaWithScreensDTO{ private Long cinemaid; private String cinemaName; private List<ScreenDTO> screens = new ArrayList<>();; public CinemaWithScreensDTO(Cinema cinemaPG) { this.cinemaid = cinemaPG.getId(); this.cinemaName = cinemaPG.getName(); // this.screens = cinema.getScreens(); } // Screen 정보를 DTO에 추가하는 메서드 public void addScreen(Screen screen) { ScreenDTO screenDTO = new ScreenDTO(screen); this.screens.add(screenDTO); } } @Data public static class ScreenDTO{ private Long screenId; private String screenName; private List<ShowtimeDTO> showtimes; public ScreenDTO(Screen screenPG) { this.screenId = screenPG.getId(); this.screenName = screenPG.getName(); showtimes = new ArrayList<>(); for (Showtime showtime : screenPG.getShowtimes()){ showtimes.add(new ShowtimeDTO(showtime)); } } } @Data public static class ShowtimeDTO{ private Long showtimeId; private String movieName; private Integer runtime; private String startedAt; public ShowtimeDTO(Showtime showtimePG) { this.showtimeId = showtimePG.getId(); this.movieName = showtimePG.getMovie().getMovieNm(); this.runtime = showtimePG.getMovie().getRuntime(); this.startedAt = new SimpleDateFormat("HH:mm").format(showtimePG.getStartedAt()); } } }
영화관을 우선으로 받고 lazy로딩으로 중복되는 데이터가 들어오는걸 확인하여 영화관따로 상영관이랑 상영시간을 따로 dto로 받기로 결정했다.
 
notion image
 

집고 넘어가기

 
@Query("SELECT s FROM Screen s LEFT JOIN FETCH s.showtimes st LEFT JOIN FETCH st.movie " + "WHERE s.id IN :screenIds AND (st.startedAt IS NULL OR CAST(st.startedAt AS date) = :selectedDate)") List<Screen> findScreensWithShowtimesByIdsAndDate(@Param("screenIds") List<Long> screenIds, @Param("selectedDate") LocalDate selectedDate);

1.H2와 Mysql의 차이로 인한 cast사용 - 추후 변경필요

  • H2 데이터베이스에서는 CAST 구문을 사용하여 Timestamp 값을 Date로 변환하는 방식으로 필터링이 가능합니다.
  • MySQL에서는 DATE() 함수를 사용하여 Timestamp에서 날짜 부분만 추출하여 비교하는 것이 일반적입니다.
  • H2로 테스트할 때는 CAST(st.startedAt AS date)을 사용하고, MySQL에서는 DATE(st.startedAt)로 변경해야 한다.
 

2. JPQL의 제한 사항:

  • JPQL에서는 JOIN에 대한 ON 절을 활용할 수 있는 기능이 제한적입니다. 따라서, ON 절에서의 조건 필터링을 수행하지 못하는 경우, WHERE에서 필터링해야한다.
  • 따라서, 상영시간이 없는 상영관도 포함하려면 LEFT JOIN을 사용하고, 상영시간이 있는 경우에만 추가 필터링을 해야 합니다.

3. LEFT JOIN의 한계:

  • LEFT JOIN FETCH는 상영시간이 없는 상영관도 가져옵니다. 하지만, WHERE에서 st.startedAt 조건으로 필터링할 경우, 상영시간이 없는 상영관들이 제외됩니다. 이는 WHERE 절에서 필터링이 JOIN 후에 적용되기 때문입니다.
  • JPQL의 한계로 인해 ON 절에서 필터링하는 방식 대신 WHERE 절에서 필터링을 수행해야 했습니다. 이를 해결하기 위해서는 상영시간이 없는 상영관도 포함시키면서도 st.startedAtNULL이거나, 선택된 날짜와 일치하는 데이터를 가져오도록 조건을 설정해야 합니다.

4. 쿼리 작성 방식:

  • 최종 쿼리에서 상영시간이 없는 상영관도 가져오고, 상영시간이 있는 경우 해당 날짜와 일치하는 상영관만 가져오도록 다음과 같은 조건을 사용했습니다:
 
@Query("SELECT s FROM Screen s LEFT JOIN FETCH s.showtimes st LEFT JOIN FETCH st.movie " + "WHERE s.id IN :screenIds AND (st.startedAt IS NULL OR CAST(st.startedAt AS date) = :selectedDate)") List<Screen> findScreensWithShowtimesByIdsAndDate(@Param("screenIds") List<Long> screenIds, @Param("selectedDate") LocalDate selectedDate);
  • st.startedAt IS NULL: 상영시간이 없는 경우를 처리하여 상영관이 제외되지 않도록 합니다.
  • CAST(st.startedAt AS date) = :selectedDate: 상영시간이 있는 경우, 해당 날짜와 일치하는 상영시간만 가져옵니다.

5. 상영시간이 없는 상영관도 가져오는 이유:

  • LEFT JOIN FETCH를 사용하여 상영시간이 없는 상영관들도 포함합니다.
  • 그러나 WHERE 절에서 날짜 조건을 걸면, 상영시간이 없는 상영관들이 필터링되어 제외될 수 있으므로, 이를 방지하기 위해 상영시간이 없는 경우에도 가져오기 위한 조건 st.startedAt IS NULL을 추가했습니다.

6. List로 처리하는 이유:

  • JPQL에서 LEFT JOIN FETCH를 사용하면 여러 개의 상영시간을 가진 데이터를 가져올 수 있기 때문에, 리스트로 반환한 후 각 상영관에서 필요한 마지막 상영시간을 처리할 수 있습니다.
  • 상영시간이 여러 개인 경우, 결과를 리스트로 받아서 별도로 필터링을 수행해야 합니다.

이 내용을 기반으로 최종 쿼리는 MySQL로 이전할 때 CAST(st.startedAt AS date)DATE(st.startedAt)로 변경해야 하며, 상영시간이 없는 상영관도 포함시키면서 필터링이 필요한 경우에는 WHERE 절에서 적절히 처리하도록 설계되었다.
 
Share article

강재영 블로그