[ClassFlix] EP 11. view 페이지 제작과 컨트롤러 연결 - 6 : 리뷰 삭제 구현
Project/ClassFlix

[ClassFlix] EP 11. view 페이지 제작과 컨트롤러 연결 - 6 : 리뷰 삭제 구현

목차 (클릭시 해당 목차로 이동)


     

     

     

    구현계획

     

    1. view에서 삭제버튼을 누를시 해당 리뷰의 id, 강의의 id를 컨트롤러에 넘깁니다.
    2. 컨트롤러에서 reviewService.delete에 리뷰의 id, 강의의 id 를 넘깁니다.
    3. lectureService에 리뷰의 id, 강의의 id 넘깁니다.
      강의 id로 해당 강의를 찾고 그 강의에 리뷰의 id 를 넘겨서 해당 id를 갖고있는 review를 제거합니다.
    4. reviewRepository에 리뷰의 id 를 넘겨서 엔티티를 찾고 EntityManager를 통해 해당 리뷰엔티티를 제거합니다.

     

     

     

    실제 구현

    view

    lecture.html

     

    url : /lectgures/{lectureId}/removeReview/{reviewId}

    method : post

    pathVariable로 아이디 2개를 넘겨줍니다.

    <form th:action="@{/lectures/{lectureId}/removeReview/{reviewId}(lectureId=${lectureId}, reviewId=${reviewDto.getReviewId()})}" method="post">
    	<button type="submit" class="btn btn-primary" role="button">삭제</button>
    </form>

     

     

    Controller

    LectureController

     

    mapping : /lectgures/{lectureId}/removeReview/{reviewId}

    method : post

     

    pathVariable로 강의, 리뷰의 아이디를 받고 service에 넘겨줍니다.

    또한 PRG패턴을 적용해 해당 강의로 다시 redirect하도록 합니다.

        @PostMapping("/lectures/{lectureId}/removeReview/{reviewId}")
        private String removeReview(@PathVariable("lectureId") Long lectureId, @PathVariable("reviewId") Long reviewId, RedirectAttributes redirectAttributes) {
            reviewService.delete(reviewId, lectureId);
            redirectAttributes.addAttribute("lectureId", lectureId);
            return "redirect:/lectures/{lectureId}";
        }

     

     

     

    Service

    ReviewService

     

    lecture에서 해당 리뷰를 삭제하도록 lectureService에 아이디 두개를 넘겨주고

    reviewRepository에 reviewId를 넘겨줍니다.

        // 리뷰 삭제
        public Long delete(Long reviewId, Long lectureId) {
            lectureService.deleteReview(lectureId, reviewId);
            reviewRepository.delete(reviewId);
            return reviewId;
        }
    

     

    LectureService

     

    lectureId를 이용해 해당 강의를 찾고

    그 강의에 reviewId넘깁니다.

        // delete review
        public void deleteReview(Long lectureId, Long reviewId) {
            Lecture findLecture = findById(lectureId);
            findLecture.removeReview(reviewId);
        }

     

    Lecture

     

    시간순으로 이해하는게 좋기 때문에 Lecture의 코드를 여기서 설명합니다.

     

    removeReview에서 reviewId를 통해 reviews에서 해당 리뷰를 찾고 제거합니다.

    그리고 averageRating의 업데이트를 위해 subAverageRating을 부릅니다.

     

    subAverageRating에서는 해당 review rating이 감소할때의 average rating을 계산해 넣습니다.

        public void removeReview(Long reviewId) {
            int restReview = this.reviewNum - 1;
            if (restReview < 0) {
                throw new NotEnoughReviewException("review is empty");
            }
            this.reviewNum -= 1;
    
            Review review = new Review();
    
            for (int i = 0; i < reviews.size(); i++) {
                review = reviews.get(i);
                if (review.getId().equals(reviewId)) {
                    reviews.remove(i);
                    break;
                }
            }
            subAverageRating(review.getRating());
        }
        
            public void subAverageRating(int rating) {
            if (reviewNum == 0) {
                this.averageRating = 0;
            } else {
                this.averageRating = Math.floor(((averageRating * (reviewNum + 1)) - rating) / reviewNum);
            }
        }

     

     

     

    Repository

    reviewId를 이용해 리뷰엔티티를 찾고 EntityManager를 통해 해당 엔티티를 제거합니다.

        public Long delete(Long reviewId) {
            Review findReview = findById(reviewId);
            em.remove(findReview);
            return reviewId;
        }

     

     

     

     

    테스트

     

    로직테스트

     

     

    lecture, member, review를 생성하고 지웠을때 감소한 강의의 리뷰개수, 평균별점 정보가 맞아야하고

    리뷰가 0개일때 delete를 한다면 NotEnoughReviewException 처리가 나야합니다.

        //리뷰삭제
        @Test
        public void 리뷰삭제() throws Exception {
            //given
    
            Member member = new Member("dongho", 25, Gender.MALE);
            em.persist(member);
    
            Lecture lecture = new Lecture("jpa", "김영한", "jpa강의", LocalDateTime.now());
            em.persist(lecture);
    
            //when
            Review review1 = new Review(member,"good", 2, lecture, LocalDateTime.now());
            Long reviewId1 = reviewService.create(review1);
    
    
            reviewService.delete(reviewId1, lecture.getId());
    
            //then
            assertThat(lecture.getReviewNum()).isEqualTo(0);
            assertThat(lecture.getAverageRating()).isEqualTo(0);
            assertThrows(NotEnoughReviewException.class, () -> {
                reviewService.delete(reviewId1, lecture.getId());
            });
        }

    테스트완료!

     

     

     

    Service, Repository 로직이 변경되었으니 전체테스트도 한번 돌려봅니다.

    테스트완료!

     

     

     

    클라이언트 테스트

     

    2개의 리뷰를 등록하고

     

    하나의 리뷰를 삭제했습니다.

    해당 강의로 정상적으로 redirect되고

    강의 정보와 리뷰들이 정상적으로 반영이 되어있습니다.

     

     

    홈 화면에서도 강의정보가 반영되어 있습니다.

     

     

    또 하나의 리뷰를 삭제했고

     

     

    홈 화면에서도 반영이 잘 되어있습니다.

     

     

     

     

     

    삽질

     

    em.remove에 id를 넘겼다.

    해당 엔티티 자체를 넘겨주어야합니다.

            Review findReview = findById(reviewId);
            em.remove(findReview);

     

     

     

    강의에 달린 리뷰 제거

    성능을 위해서

    view -> controller -> service -> repository -> domain 으로 가는 동안

    엔티티가 직접 넘어가지 않고 최 말단에서 엔티티 조회를 하도록 했습니다.

     

    그래서 lecture domain 에서 해당 review를 제거하기 위해서 id를 비교했고 반복문을 통해 id를 찾고 제거했습니다.

    이는 이후 성능문제에서 고려해보아야 할 점 같습니다.

     

     

     

     

     

    의문점

    review 삭제 거부를 lecture에서 낸 예외처리로 해도 되는가

    member에 리뷰도 연관되어있는데 연관관계 메소드 처리를 안해도 되는가

    lecture의 review를 제거할때 반복문을 통해 제거해도되는가

     

     

     

    리팩토링

    에러발생페이지 지정