프로젝트

TIL-221121 - 페이지네이션을 직접 구현할 필요가 있나?

혹등고래1호기 2022. 11. 21. 23:32

 

야심한 새벽 어느 한 망자가 인수테스트를 하면서 겪었던 실화를 바탕으로 글을 작성해 봅니다.

 

 

이런 식으로 상품 상세 페이지에서는 베스트 리뷰를 상단에 배치하고 일반 리뷰는 상품의 상세 설명이 끝나는 밑에 배치를 했다. 

 

둘다 Review라는 똑같은 entity이지만 

 

boolean으로 Field값을 줘 true이면 베스트 리뷰이고 false이면 일반 리뷰로 분류를해 상품 상세 페이지가 마운트 되었을 떄 

일반 리뷰와 베스트리뷰를 각각의 API요청을 통해 받아오는 형식으로 구현을 해놓은 상황이였다. 

 

 

그렇게 페이지네이션에 대한 인수테스트를 작성을 하고 테스트를 하는데 계속 해서 실패를 해서 UI를 통해 상황을 확인해 보니

 

 

일반 리뷰는 없다? 

 

 

하지만 db에는 backdoorAPI를 통해 만든 일반 리뷰들이 5개가 잘 들어와있다. 

 

그렇게 일반 리뷰를 찾아 떠나 3만리 프론트 엔드에서는 API요청을 통해 들어오는 일반리뷰 자체가 없었다(Array의 length가 0인 상황이다.) 그렇다면 백엔드 쪽에서 데이터를 못 줬다는 것인데

 

 Sort sort = Sort.by("id");
        pageable = PageRequest.of(page - 1, 4, sort);

        Page<Review> reviews =
            reviewRepository.findAllByProductId(productId, pageable);

        List<ReviewDto> reviewDtos = reviews.stream()
            .filter(review -> review.isBestReview().equals(false))
            .map(review -> review.toDto(
                reviewImageDtos(review),
                recommendationDtos(review))).toList();

        int pages = this.pages(productId);

        double totalRating = this.totalRating(productId);

        int totalReviewNumber = reviewRepository.findAllByProductId(productId).size();

        return new ReviewDtos(reviewDtos, pages, totalRating, totalReviewNumber);
    }

 

이런 식으로 JPA의 pageable객체를 사용하여 페이지네이션을 구현한 상황이 였는데 

이 부분에서 문제가 있었다. bestReview를 불러오는 쪽에서도 똑같이 findAllByProductId메소드를 사용하고 있는데

일반 리뷰는 못 불러오고 베스트 리뷰는 만 되었던 이유는

 

Review는 정렬을 id를 내림차순으로 정렬을 해놓았고 한 페이지에 4개의 review를 불러 오게 했으니 아이디가 1,2,3,4인 review들을 들고 오는데

 

 

아이디가 1,2,3,4인 친구들은 모두 베스트 리뷰 친구들만 있는 상황이 였기 때문에 일반 리뷰는 불러오기 못하는 상황이 발생했었던 것이다. 

 

하나의 엔티티에 대해서 field값에 따라서 페이지네이션을 각각 따로 해줘야하는 상황에는 아마 페이지네이션을 직접 구현을 해야하는 케이스 일 것 같다.

 

페이지네이션 구현 방법

 List<Review> reviewsPerPage = new ArrayList<>();

        List<Review> reviews =
            reviewRepository.findAllByProductId(productId)
                .stream().filter(review -> !review.isBestReview())
                .toList();

        for(int i = 4 * (page - 1); i < page * 4; i+=1) {
            if(reviews.size() == i) {
                break;
            }

            reviewsPerPage.add(reviews.get(i));
        }

        List<ReviewDto> reviewDtos = reviewsPerPage.stream()
            .map(review -> review.toDto(
                reviewImageDtos(review),
                recommendationDtos(review))).toList();

        int pages = reviews.size() / 4;

        if(reviews.size() % 4 != 0) {
            pages = reviews.size() / 4 + 1;
        }

        double totalRating = this.totalRating(productId);

        int totalReviewNumber = reviewRepository.findAllByProductId(productId)
            .size();

        return new ReviewDtos(reviewDtos, pages, totalRating, totalReviewNumber);

 

일단은 일반 리뷰를 담아 줄 리스트를 인스턴스해준다.

 

그리고 모든 리뷰를 불러온뒤 isBestReview가 false값인 친구들만 Stream API를 통해서 따로 리스트를 만들어 준다. 

 

그리고 한 페이지에 보여줄 review의 갯수만큼 리스트에 더해 준다. 

for문은 페이지가 1이면 리스트에서 0~3번째 인덱스를 얻어오고 2페이지이면 4 ~ 7번째 리뷰를 불러오는 것이다.

 

그리고 DTO로 변환 해준뒤 모든 리뷰 나누기 한 페이지에 보여줄 갯수 를 하여 나머지가 없으면 그대로 나눈값을 리턴하고

 

나머지가 있으면 + 1을 해줘서 전체 페이지 수를 구하면 

 

끝!!!!

 

단위 테스트에서는 props를 모킹해서 테스트를 해주니까 당연히 백엔드에서 정보를 알맞게 보내 줄 것이라고 생각을 했는데 인수테스트를 통해 알게 되었다. 

 

반드시 인수 테스트를 작성하도록하자