플러스 프로젝트 어려웠던부분

김현찬·2025년 7월 11일

도서 리뷰 평점 평균을 구해 순위를 집계하는 로직을 작성했습니다.

처음 생각했던 구조

  • 도서 총 리뷰 수를 집계하는 쿼리를 작성하여 통계 엔티티로 반환
  • 도서 평점 평균을 집계하는 쿼리를 작성하여 통계 엔티티로 반환
  • 평점 평균 포매팅

변경 이유

집계 결과를 엔티티로 반환하여 그대로 사용하려했는데, 집계 쿼리의 경우 계산된 값을 엔티티에 직접 매핑할 수 없었습니다.

  1. 엔티티의 컬럼에 값을 넣어볼까? 생각하니 반환 값이 딱 떨어지는 하나의 값이 아닌, 각 도서별 집계 결과여서 하나의 컬럼에 값을 넣는것도 불가능하다고 판단했습니다.

  2. 그래서 List에 엔티티를 담아서 따로 반환하면 되지 않을까? 싶어서 시도 해봤지만, 집계 결과와 엔티티가 매핑되지 않아 다른 방법을 모색했습니다.

마땅한 방법이 떠오르지 않아서 구글에 '집계 쿼리 결과 매핑'으로 키워드라도 얻고자 추상적으로 검색했는데, 구글 AI가 해결 방법을 알려주었습니다.

사용 방법과 예시도 작성되어 있었지만, 처음 접하다보니 관련 내용을 좀 더 찾아보았고 한 블로그를 찾았습니다.

참고 블로그 링크

제가 사용하고자 하는것이 JPA이므로 해당 블로그 목차 2번을 살펴보았고

다른 블로그도 찾아보았습니다.

추가 참고 블로그 링크

SELECT 구문에 new DTO경로 (조회 컬럼) 이와 같은 방식으로 사용한다는것을 알았습니다.

이 과정에서 두 개 였던 집계 쿼리를 하나로 합치게 되었는데, 그 이유는 두 개의 집계 쿼리를 매핑할 DTO를 각각 따로 만들게 될 경우 두 개의 리스트에서 도서 관련 정보를 처리하는 과정이 중복된다고 느꼈고 이를 어떻게 처리하면 좋을지 고민하던 중 쿼리를 하나로 합치게 되었습니다.

또한 기존 구조에서는 통계 테이블에서 도서 정보에 접근하고, 해당 도서 정보를 바탕으로 리뷰 정보에 접근하려 했으나, 도서 -> 리뷰 연관관계가 존재하지 않고, 리뷰 -> 도서 연관관계만 존재한다는 사실을 인지했습니다.

하여 리뷰 테이블을 기준으로 도서 정보를 조회하도록 쿼리를 변경하였습니다.

최종 쿼리

    @Query("SELECT new com.example.bookify.domain.statistics.service.dto.AvgReviewGradeDto(b, AVG (r.grades), COUNT (r)) " +
            "FROM Review r " +
            "JOIN r.book b " +
            "WHERE r.updatedAt BETWEEN :startDate AND :endDate " +
            "GROUP BY r.book " +
            "ORDER BY AVG (r.grades) DESC, COUNT (r) DESC")
    List<AvgReviewGradeDto> avgByReviewByGrades(@Param("startDate") LocalDateTime startDate,
                                                @Param("endDate") LocalDateTime endDate
    );

추가적으로 평점 평균 포매팅은 아직까지 평점 평균을 반환하지 않으므로 보류하였습니다.

순위 중첩 문제

        Long reviewRank = 1L;

        for (AvgReviewGradeDto gradeRank : avgGradesReview) {
            Statistics ratingRanking = new Statistics(
                    reviewRank++,
                    gradeRank.getAvgGrade(),
                    gradeRank.getCountReview(),
                    gradeRank.getBook()
            );
            statisticsRepository.save(ratingRanking);
        }

위 코드가 스케줄러에 의해 5분 주기로 실행되어 이미 집계 된 정보를 중복하여 집계하는 문제가 발생했습니다.

따라서 해당 월의 통계를 지우는 로직을 위 코드 위에 작성하면, 해당 월의 통계가 삭제되고 최신 정보를 반영한 채 다시 집계되어 저장되는 효과를 기대했습니다.

    @Query("DELETE FROM Statistics s WHERE s.createdAt BETWEEN :startDate AND :endDate")
    void deleteStatistics(@Param("startDate") LocalDateTime startDate,
                          @Param("endDate") LocalDateTime endDate
    );

하여 통계 삭제 쿼리를 작성한 후 실행하였는데 오류가 발생하며 프로그램이 종료되었습니다.

	@Modifying
    @Query("DELETE FROM Statistics s WHERE s.createdAt BETWEEN :startDate AND :endDate")
    void deleteStatistics(@Param("startDate") LocalDateTime startDate,
                          @Param("endDate") LocalDateTime endDate
    );

위와 같이 수정하니 프로그램이 의도한대로 작동하였습니다.

이유 = @Query는 기본적으로 SELECT 구문일것이라 가정합니다.
따라서 조회 쿼리가 아니라 DB 상태를 변경하는 쿼리라는것을 명시적으로 지정해줘야 합니다.

0개의 댓글