image

리뷰를 최신순/평점순으로 정렬해 보았다. 구현에 사용된 클래스들과 html을 하나씩 살펴보도록 하자. 화면은 부트스트랩과 css를 사용하여 구성하였다.

ReviewMapper

파라미터 값으로 넘겨주는 Criteria의 멤버변수로는 page, movieNo, option, beginIndex, endIndex가 있다. 페이지네이션 시 필요한 값과 넘겨받는 영화번호에 해당하는 리뷰만 출력하도록 sql 구문을 작성하였다. option에는 date(최신순), rate(평점순) 값을 jsp 쪽에서 넘겨주도록 할 것이다.

    <select id="getAllReviewsByMovieNo" parameterType="com.example.web.form.Criteria" resultType="com.example.dto.ReviewDto">
            select 
                review_no reviewNo,
                customer_id customerId,
                review_score reviewScore,
                review_content reviewContent
            from 
                (select 
                    row_number() over (order by 
                    <choose>
                        <when test="option == 'date'">r.review_no desc</when>
                        <when test="option == 'rate'">r.review_score desc</when>
                    </choose>
                    ) rn,
                    r.review_no, c.customer_id, r.review_score, r.review_content
                    from customer c, review r, movie m
                where 
                        r.movie_no = m.movie_no
                        and c.customer_no = r.customer_no
                    and m.movie_no = #{movieNo})
            where 
                rn between #{beginIndex} and #{endIndex}
    </select>

Controller

컨트롤러에서 페이지네이션 시에 필요한 객체를 생성하였다. 페이지네이션 객체는 링크에서 확인할 수 있다. 컨트롤러는 restController로 설정하였고, 데이터의 정형화와 json 텍스트로 전달하기 위해서 ResponseDto로 반환하였다. restController는 요청핸들러 메소드가 반환하는 값을 응답메세지의 body에 포함시킨다. 나는 프로젝트에 jackson-databind 라이브러리가 포함되어 있기 때문에 요청핸들러 메소드가 반환하는 값이 json 형식의 텍스트로 변환된 다음 전달된다.

ajax로 전달받은 response를 콘솔창에 찍어 보니 다음과 같이 items 안에 option, pagination, reviews, totalReviews가 name / value의 형태로 전달된 것을 확인할 수 있다.

image

    @RestController
    @RequestMapping("/rest")    
    public class ReviewRestController {

        @GetMapping("/review")
        public ResponseDto<Map<String, Object>> reviews(Criteria criteria) {
            if (criteria.getPage() < 1) {
                criteria.setPage(1);
            }
            
            // 영화번호에 해당하는 리뷰의 총 개수
            int totalRecords = reviewService.getTotalRecords(criteria.getMovieNo());
            Pagination pagination = new Pagination(criteria.getPage(), totalRecords);
            
            // 페이지네이션에서 구한 시작페이지와 끝페이지를 세팅한다
            criteria.setBeginIndex(pagination.getBegin());
            criteria.setEndIndex(pagination.getEnd());
            String option = criteria.getOption();
            
            ResponseDto<Map<String, Object>> response = new ResponseDto<>();
            List<ReviewDto> reviews = reviewService.getAllReviewsByMovie(criteria);
            int totalReviews = reviewService.getAllReviewsByMovie(criteria).size();
            
            // json 형태로 전달하기 위해 responseDto를 정의하여 map 객체로 화면 구현에 필요한 객체들을 반환한다
            response.setStatus(true);
            response.setItems(Map.of("pagination", pagination, "reviews", reviews, "totalReviews", totalReviews, "option", option));
            
            return response;
        }
    }

jsp

input 태그에 name을 option으로, value를 각각 date, rate로 설정해 주었다. script 태그에서는 name이 option인 input 태그를 클릭했을 때 클릭한 값을 order 변수에 전달해 준다.

    // 간략화한 html
    <div class="row">
        <div class="movie-sorting text-end">
            <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
                // input에 name과 value를 설정해 준다.
                <input type="radio" class="btn-check" name="option" value="date" id="date" autocomplete="off" checked>
                <label class="btn btn-outline-dark btn-sm" for="date">최신순</label>
                <input type="radio" class="btn-check" name="option" value="rate" id="rate" autocomplete="off">
                <label class="btn btn-outline-dark btn-sm" for="rate">평점순</label>
            </div>
        </div>
    </div>
    
    <script>
    let currentPageNo = 1;
    getReviews();

    // 최신순, 평점순 정렬
    $(":input[name=option]").click(function() {
        // 최신순이나 평점순 버튼을 클릭했을 때 페이지의 번호를 다시 1로 초기화시켜 준다.
        currentPageNo = 1;

        // review 목록이 들어가는 곳의 div를 empty로 지워준다.
        $(".review-box").empty();

        // getReviews() 함수로 옵션에 해당하는 리뷰 목록을 다시 div에 붙인다.
        getReviews();
    })

    function getReviews() {
        // 선택한 input 태그의 값을 order 변수에 저장한다.
        let order = $("[name=option]:checked").val();

        // 리뷰 출력 시 필요한 값들을 전달한다.
        $.getJSON('/rest/review', {page: currentPageNo, option: order, movieNo: movieId}, function(response) {
            let reviews = response.items.reviews;
            
            // 리뷰가 없을 때
            if (reviews == "") {
                $(".no-review").text("에는 아직 등록된 리뷰가 존재하지 않습니다.");
            // 리뷰가 있을 때
            } else {
                // 리뷰 목록을 출력하는 코드는 너무 길어서 생략
            }
        }
    }
    </script>