SeatController
    @GetMapping("/api/seat")
    public String seat(HttpServletRequest request) {
        long showtimeId = (long) session.getAttribute("showtimeId");
        SeatResponse.DTO model = seatService.좌석메인화면(showtimeId);
        request.setAttribute("model", model);
        return "seat/view";
    }좌석메인화면에 필요한 데이터를 조회하기 위해 showtimeId 가 필요한데
session으로 전달받아서 사용하였다.
SeatService
   public SeatResponse.DTO 좌석메인화면(long id) {
        Showtime showtimePS = showtimeRepository.mFindById(id)
                .orElseThrow(() -> new Exception404("상영시간정보가 없습니다."));
        Long screenId = showtimePS.getScreen().getId();
        Screen screenPS = screenRepository.mFindAllById(screenId)
                .orElseThrow(() -> new Exception404("상영관정보가 없습니다."));
        Integer totalSeat = seatRepository.mFindCountOfTotalSeat(id);
        if(totalSeat == null || totalSeat == 0){
            throw new Exception404("좌석 정보가 없습니다.");
        }
        Integer reservedSeat = ticketRepository.mFindCountOfReservedSeats(id);
        if(reservedSeat == null){
            reservedSeat = 0;
        }
        //System.out.println("예약된 좌석 수 " + reservedSeat);
        return new SeatResponse.DTO(showtimePS, screenPS, totalSeat, reservedSeat);
    }
null이나 0이 return 되는 경우 
GlobalExceptionHandler 로 throw 해서 자바스크립트 alert 창이 뜨도록 처리하였다.SeatRepository
    @Query("select st from Showtime st left join fetch st.movie mt where st.id=:id")
    Optional<Showtime> mFindById(@Param("id") Long id);컨벤션을 지키기 위해 m 을 매서드 이름 앞에 붙였다.
SeatResponse
    @Data
    public static class DTO {
        // showtime
        private String startedAt; // 시작 시간
        private String endedAt; // 끝나는 시간
        private String wholeShowTime; // 일자 + 시간
        private Integer price; // 영화 가격 ( 1좌석당, 1티켓당 )
        // movie
        private Integer runtime; // 영화 러닝타임
        private String movieNm; // 영화 pk
        private String ratingGrade; // 등급
        //poster
        private String posterUrl; // 포스터
        // screen
        private String screenNm; // 상영관 pk
        // cinema
        private String cinemaNm; // 영화관 pk
        private String cinemaImg; // 영화관 이미지
        // seat count
        private Integer totalSeats;
        private Integer remainingSeats;
        public DTO(Showtime showtimePS, Screen screenPS, Integer totalSeat, Integer reservedSeat) {
            String formatGrande = "yyyy.MM.dd(E)";
            String formatPequeno = "HH:mm";
            String formatTotal = "yyyy.MM.dd(E) HH:mm";
            //showtimePS.getStartedAt(); // 2024-09-12 14:00:00.0
            // 시작 시간 형식 변환
            this.startedAt = convertTimeStampToString(showtimePS.getStartedAt(), formatGrande);
            // 종료 시간 계산 ( 시작 시간 + runtime )
            this.endedAt = calculateEndTime(showtimePS.getStartedAt(), showtimePS.getMovie().getRuntime()); // 종료 시간 계산
            // 전체 시간 형식 변환
            this.wholeShowTime = convertTimeStampToString(showtimePS.getStartedAt(), formatTotal);
            this.price = showtimePS.getPrice();
            this.runtime = showtimePS.getMovie().getRuntime();
            this.movieNm = showtimePS.getMovie().getMovieNm();
            this.posterUrl = showtimePS.getMovie().getPosterUrls().get(0).getUrl();
            String grade = showtimePS.getMovie().getRatingGrade();
            if(grade == "전체"){
                this.ratingGrade = "전체 관람가";
            }else{
                this.ratingGrade = grade + " 관람가";
            }
            this.screenNm = screenPS.getName();
            this.cinemaNm = screenPS.getCinema().getName();
            this.cinemaImg = screenPS.getCinema().getImgName();
            // 좌석 수
            this.totalSeats = totalSeat;
            this.remainingSeats = totalSeats - reservedSeat;
            System.out.println(remainingSeats);
        }
        // Timestamp를 지정한 format으로 변환하는 매서드
        public static String convertTimeStampToString(Timestamp timestamp, String format) {
            if(timestamp == null) return "";
            try {
                Date date = new Date();
                date.setTime(timestamp.getTime());
                //return new SimpleDateFormat(format).format(date);
                // Locale 설정으로 요일을 한국어로 출력
                SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.KOREAN);
                return sdf.format(date);
            } catch (Exception e) {
                throw new Exception404("날짜 정보가 뭔가 잘못되었습니다.");
            }
        }
        // 시작 시간에 runtime을 더해 종료 시간을 계산하는 매서드
        public static String calculateEndTime(Timestamp startedAt, int runtimeMinutes){
            LocalDateTime startDateTime = startedAt.toLocalDateTime(); // TimeStamp를 LocalDateTime으로 변환
            LocalDateTime endDateTime = startDateTime.plus(runtimeMinutes, ChronoUnit.MINUTES); // runtime을 분 단위로 더함
            // HH :mm 형식으로 변환
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
            return endDateTime.format(formatter);
        }
    }좌석 페이지에 필요한 정보들을 담았다 
좌석은 제외.
→ 좌석 코드까지 들어가니 코드가 너무 길어져서 좌석을 제외한 정보만 담았다.
→ 좌석은 AJAX 로 요청해서 SeatDTO 에 담아서 return 하도록 코드를 분리하였다.
seatView.mustache
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>title</title> <!-- 좌석 선택 -->
    <!-- 부트스트랩 CSS 링크 -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/css/seat.css">
    <link rel="stylesheet" href="/css/header.css">
</head>
<body>
{{>layout/header}}
<main>
    <div id="hidden-data" data-price="{{model.price}}" data-showtimeid="{{showtimeId}}"></div>
    <div class="container mt-5">
        <!-- 상단 인원 / 좌석 -->
        <div class="text-center mb-4">
            <div class="bg-dark text-white py-2 mb-3">인원 / 좌석</div>
        </div>
        <section>
            <div class="count__seat__section">
                <!-- 인원 선택 -->
                <div class="count__section">
                    <div class="mb-4">
                        <div class="text-center">
                            <label>일반</label>
                            <div class="btn-group btn-group-toggle" data-toggle="buttons">
                                <button class="btn btn-outline-primary" id="count0" value="0" onclick="resetCount()">0</button><!-- 0 클릭 시 초기화 -->
                                <button class="btn btn-outline-primary" id="count1" value="1" onclick="getCount(this.value)">1</button>
                                <button class="btn btn-outline-primary" id="count2" value="2" onclick="getCount(this.value)">2</button>
                                <button class="btn btn-outline-primary" id="count3" value="3" onclick="getCount(this.value)">3</button>
                                <button class="btn btn-outline-primary" id="count4" value="4" onclick="getCount(this.value)">4</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="vertical__line"></div>
                <div class="movie__info__section">
                    <!-- 상영 정보 -->
                    <div class="text-center mb-4">
                        <img class="screen__img" src="/img/cinema01.jpg"><!-- model.cinema 이미지로 수정 -->
                        <p class="font-weight-bold">{{model.cinemaNm}} | {{model.screenNm}} | 남은좌석 {{model.remainingSeats}}/{{model.totalSeats}}</p>
                        <p>{{model.wholeShowTime}} ~ {{model.endedAt}}</p><!-- 2024.09.04 (수) 20:15 -->
                    </div>
                </div>
            </div>
        </section>
        <!-- 좌석 배치 -->
        <div class="seat-selection text-center mb-4">
            <div class="bg-dark text-white py-2 mb-3">SCREEN</div>
            <div id="seat-container"></div>
            <!-- 좌석 반복 추가 -->
        </div>
    </div>
    <!-- 하단 검은창 -->
    <div class="row bg-dark text-white py-2 mb-3">
        <div class="col text-left">
            <button class="btn btn-secondary">영화선택</button>
        </div>
        <div class="blackbox__middle__container">
            <div class="blackbox__img__div">
                <img class="movie__img" src="{{model.posterUrl}}">
            </div>
            <div class="blackbox__movie__div">
                <div>{{model.movieNm}}</div>
                <div>{{model.ratingGrade}}</div>
            </div>
            <div>
                <table>
                    <tr>
                        <td class="left__td">극장</td>
                        <td class="right__td">{{model.cinemaNm}}</td>
                    </tr>
                    <tr>
                        <td>일시</td>
                        <td>{{model.wholeShowTime}}</td><!-- 2024.9.14(토) 12:00 -->
                    </tr>
                    <tr>
                        <td>상영관</td>
                        <td>{{model.screenNm}}</td>
                    </tr>
                    <tr>
                        <td>인원</td>
                        <td id="peopleNum"></td><!-- js로 -->
                    </tr>
                </table>
            </div>
            <div class="seat__num__box" id="seatNumBox">
                <!-- JS 로 동적으로 넣기 -->
            </div>
            <div class="seat__payment__box" id="paymentBox">
                <!-- JS 로 동적으로 넣기 -->
            </div>
        </div>
        <div class="col text-right">
            <form action="/api/seat/reservation" method="post">
                <!--<input type="hidden" name="showtimeId" value="">-->
                <input type="hidden" name="showtimeId" value="{{showtimeId}}">
                <input type="hidden" name="selectedSeatsIds" id="selectedSeatsIds">
                <input type="hidden" name="totalPrice" id="totalPrice">
                <button onclick="return didYouSelectAll()" class="btn btn-secondary" type="submit">결제선택</button>
            </form>
        </div>
    </div>
    <!-- 부트스트랩 JS 및 jQuery -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
    <script src="/js/seat2.js"></script>
</main>
{{>layout/footer}}
</body>
</html>
좌석을 제외한 부분의 정보가 잘 들어왔다.
Share article