Skip to content

Conversation

@kon28289
Copy link
Contributor

@kon28289 kon28289 commented Sep 21, 2025

What is this PR?🔍

  • 상품 목록 조회 시 리뷰 평점과 리뷰 개수를 함께 전달합니다.
  • 리뷰 도메인의 평점의 자료형을 Double로 변경하여 실수를 입력받도록 합니다.

Changes💻

ScreenShot📷

Summary by CodeRabbit

  • 신기능

    • 상품 목록에 판매자 평균 평점과 리뷰 수가 표시됩니다.
    • 판매자 신뢰도를 한눈에 확인하고, 상품별 리뷰 규모를 빠르게 파악할 수 있습니다.
  • 개선

    • 리뷰 평점 표시가 정수에서 소수점 단위로 향상되어 보다 정확한 평점을 확인할 수 있습니다.
    • 상품 목록과 리뷰 관련 정보가 더 풍부해져 탐색 및 비교가 쉬워졌습니다.

@kon28289 kon28289 self-assigned this Sep 21, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Walkthrough

ProductListResponse에 sellerRating과 reviewsCount가 추가되었고, 리포지토리 조회(getAllProducts)에서 리뷰 기반의 평균 평점과 리뷰 수를 서브쿼리로 계산해 프로젝션에 포함했습니다. 리뷰 도메인과 DTO의 rating 타입이 Long에서 Double로 변경되었습니다.

Changes

Cohort / File(s) Change Summary
Product 리스트 응답 확장
src/main/java/com/uhdyl/backend/product/dto/response/ProductListResponse.java
레코드 필드 추가: sellerRating: Double, reviewsCount: Long. 생성자 시그니처에 zzimCount, sellerRating, reviewsCount 반영 및 필드 매핑.
상품 조회 프로젝션 보강
src/main/java/com/uhdyl/backend/product/repository/CustomProductRepositoryImpl.java
QReview review 도입. 서브쿼리로 판매자 평균 평점(review.rating.avg().coalesce(0.0)) 및 상품 리뷰 수(review.count()) 계산 후 ProductListResponse 프로젝션에 추가. 기존 조인/필터는 유지.
리뷰 도메인 타입 변경
src/main/java/com/uhdyl/backend/review/domain/Review.java
rating 타입 Long → Double. 생성자/빌더 시그니처를 Double 기반으로 갱신.
리뷰 생성 요청 DTO 타입 변경
src/main/java/com/uhdyl/backend/review/dto/request/ReviewCreateRequest.java
rating 필드 타입 Long → Double. 레코드 시그니처 갱신.
리뷰 응답 DTO 타입 변경
src/main/java/com/uhdyl/backend/review/dto/response/ReviewResponse.java
rating 필드 타입 Long → Double로 변경. 주석 정리.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant S as ProductService
  participant R as CustomProductRepositoryImpl
  participant DB as Database

  C->>S: getAllProducts(query)
  S->>R: fetch products with projections
  R->>DB: SELECT products + joins (user, image)
  DB-->>R: product rows

  rect rgba(200,230,255,0.3)
    note over R: For each product
    R->>DB: SUBQUERY avg(review.rating) WHERE targetUserId=user.id AND productId=product.id
    DB-->>R: sellerRating (avg or 0.0)
    R->>DB: SUBQUERY count(review) WHERE targetUserId=product.user.id AND productId=product.id
    DB-->>R: reviewsCount
  end

  R-->>S: List<ProductListResponse>(... sellerRating, reviewsCount ...)
  S-->>C: Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

밤하늘 코드에 별점이 반짝🌟
리뷰의 파도는 Double로 살짝—찰싹
토끼는 귀 세워 평균을 셈하고
카운트 콩콩! 숫자를 껑충 안고
목록 응답에 담아 폴짝 납품 끝! 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning 현재 PR 설명은 What is this PR? 섹션에서 변경 목적을 간단히 기술했으나 Changes 섹션에 구체적인 변경 항목이 비어있고 ScreenShot 섹션도 작성되지 않아 템플릿 요건을 충족하지 못합니다. Changes 섹션에 각 변경 파일 및 내용 항목을 구체적으로 나열하고 필요 시 동작 화면을 캡처하여 ScreenShot 섹션에 첨부해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "Feat: 상품 목록 조회 필드 추가"는 상품 목록 조회에 새로운 필드를 추가한다는 주요 변경 사항을 명확하게 요약하고 있으며 간결하고 명확한 문장으로 작성되어 있어 스캔 시 이해하기 쉽습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/product

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d64ffb1 and 6f251fb.

📒 Files selected for processing (5)
  • src/main/java/com/uhdyl/backend/product/dto/response/ProductListResponse.java (2 hunks)
  • src/main/java/com/uhdyl/backend/product/repository/CustomProductRepositoryImpl.java (2 hunks)
  • src/main/java/com/uhdyl/backend/review/domain/Review.java (2 hunks)
  • src/main/java/com/uhdyl/backend/review/dto/request/ReviewCreateRequest.java (1 hunks)
  • src/main/java/com/uhdyl/backend/review/dto/response/ReviewResponse.java (1 hunks)
🔇 Additional comments (13)
src/main/java/com/uhdyl/backend/review/domain/Review.java (2)

25-25: rating Double 전환은 방향 OK, 다만 범위/널 검증을 명시하세요.

실수 저장은 평균 산출에 적합합니다. 동시에 유효 범위(예: 0.0–5.0)와 널 금지 제약을 엔티티 차원에서 보장하는 것이 안전합니다.
[ suggest_recommended_refactor ]

아래처럼 제약을 추가하는 것을 권장합니다:

-    private Double rating;
+    @jakarta.validation.constraints.NotNull
+    @jakarta.validation.constraints.DecimalMin(value = "0.0", inclusive = true)
+    @jakarta.validation.constraints.DecimalMax(value = "5.0", inclusive = true)
+    private Double rating;

(필요 시 DB 레벨에서도 CHECK 제약(0.0 ≤ rating ≤ 5.0) 추가를 권장)


36-36: 생성자 시그니처 변경에 따른 컴파일/마이그레이션 영향 점검 필요.

기존 Long -> Double 타입 변경으로 호출부(팩토리/매퍼/테스트)에서 암묵 변환이 불가합니다. 전역에서 Review(..., rating, ...) 호출이 모두 Double로 이행됐는지 확인해주세요. DB 컬럼 타입 변경(Flyway/Liquibase)도 누락되면 런타임 오류가 납니다.
[ request_verification ]

다음 스크립트로 호출부와 마이그레이션 존재 여부를 빠르게 점검할 수 있습니다:

#!/bin/bash
# rating 인자 호출/할당 위치 점검
rg -n -C2 -P --type=java '\bReview\s*\('
rg -n -C2 -P --type=java '\.rating\s*\('

# 마이그레이션 파일에서 review/rating 컬럼 변경 기록 검색
rg -n -i -C2 --glob '!**/target/**' '(alter|modify|update)\s+table.*review|review.*rating|create\s+table.*review' --type-add 'sql:*.{sql,yml,xml,json}'
src/main/java/com/uhdyl/backend/review/dto/request/ReviewCreateRequest.java (1)

5-9: 요청 DTO에도 유효성 제약을 부여해 조기 차단하세요.

컨트롤러 진입 시점에 검증하는 편이 UX/보안 모두에 이롭습니다.
[ suggest_recommended_refactor ]

-package com.uhdyl.backend.review.dto.request;
+package com.uhdyl.backend.review.dto.request;
+
+import jakarta.validation.constraints.*;

 public record ReviewCreateRequest (
-        String content,
-        Double rating,
+        @NotBlank String content,
+        @NotNull @DecimalMin(value = "0.0") @DecimalMax(value = "5.0") Double rating,
         String imageUrl,
         String publicId,
-        Long productId
+        @NotNull Long productId
 ){
 }
src/main/java/com/uhdyl/backend/review/dto/response/ReviewResponse.java (1)

10-10: Double 전환은 일관성 측면에서 적절합니다.

표출 소수 자리수(예: 1자리) 고정은 쿼리/매핑 단계에서 처리하면 응답 일관성이 좋아집니다. 별도 처리 없으면 원시 평균값이 내려갑니다.
[ approve_code_changes ][ suggest_optional_refactor ]

추가로, 필드명 Id(대문자 I)는 컨벤션 상 id가 자연스럽습니다(선택).

src/main/java/com/uhdyl/backend/product/dto/response/ProductListResponse.java (2)

19-30: record에서 필드 대입하는 canonical 생성자 구현은 컴파일 실패 위험이 큽니다. 제거 권장.

Java record의 canonical 생성자에서는 필드에 직접 대입(this.id = id)이 불가합니다. 현재 구현은 JLS 위배 가능성이 높습니다. 본 클래스는 QueryDSL에서 Projections.constructor를 사용하므로 별도 생성자 없이 canonical(암시적) 생성자만 두면 충분합니다.
[ raise_critical_issue ]

아래처럼 명시 생성자를 제거하세요:

 public record ProductListResponse(
   ...
 ) {
-    @QueryProjection
-    public ProductListResponse(Long id, String title, Long price, String sellerName, String sellerPicture, String mainImageUrl, boolean isCompleted, Long zzimCount, Double sellerRating, Long reviewsCount) {
-        this.id = id;
-        this.title = title;
-        this.price = price;
-        this.sellerName = sellerName;
-        this.sellerPicture = sellerPicture;
-        this.mainImageUrl = mainImageUrl;
-        this.isCompleted = isCompleted;
-        this.zzimCount = zzimCount;
-        this.sellerRating = sellerRating;
-        this.reviewsCount = reviewsCount;
-    }
 }

(혹시 @QueryProjection 기반의 QProductListResponse를 다른 곳에서 사용 중이면 알려주세요. 그 경우 compact canonical 생성자 사용으로 전환하는 대안을 제안드리겠습니다.)


14-17: 필드 추가에 따른 모든 프로젝션 호출부 동기화 필요.

getAllProducts만 10개 파라미터로 갱신되었고, getMyProducts, searchProducts는 여전히 8개 파라미터를 사용합니다. 현재 상태로는 런타임에 일치하는 생성자를 찾지 못해 예외가 납니다.
[ raise_critical_issue ][ request_verification ]

아래 리포지토리 수정안을 참고하세요(각 메서드에서 sellerRating, reviewsCount 서브쿼리를 추가).

src/main/java/com/uhdyl/backend/product/repository/CustomProductRepositoryImpl.java (7)

66-86: getMyProducts: ProductListResponse 생성자 파라미터 불일치로 런타임 실패. 두 서브쿼리 추가 필요.

현재 8개 선택 컬럼만 투영합니다. sellerRating(Double), reviewsCount(Long) 두 컬럼을 추가해 10개로 맞춰주세요.
[ raise_critical_issue ]

     public MyProductListResponse getMyProducts(Long userId, Pageable pageable){
         QProduct product = QProduct.product;
         QImage image = QImage.image;
         QUser user = QUser.user;
         QZzim zzim = QZzim.zzim;
+        QReview review = QReview.review;

         PathBuilder<Product> entityPath = new PathBuilder<>(Product.class, "product");

         OrderSpecifier<?>[] orderSpecifiers = getOrderSpecifiers(pageable, entityPath);

         List<ProductListResponse> content = jpaQueryFactory
                 .select(Projections.constructor(ProductListResponse.class,
                         product.id,
                         product.title,
                         product.price,
                         user.nickname.coalesce(user.name).coalesce(""),
                         user.picture,
                         image.imageUrl.min(),
                         product.isSale.not(),
-                        zzim.id.count()
+                        zzim.id.count(),
+                        JPAExpressions
+                                .select(review.rating.avg().coalesce(0.0))
+                                .from(review)
+                                .where(review.targetUserId.eq(user.id)
+                                        .and(review.productId.eq(product.id))),
+                        JPAExpressions
+                                .select(review.count())
+                                .from(review)
+                                .where(review.targetUserId.eq(user.id)
+                                        .and(review.productId.eq(product.id)))
                 ))

(추가 그룹바이 불필요 — 서브쿼리는 별개 계층)

Also applies to: 57-61


277-287: searchProducts: 동일한 미스매치. 10개 컬럼으로 보강 필요.

검색 결과에서도 동일하게 두 컬럼을 추가해야 합니다.
[ raise_critical_issue ]

     public GlobalPageResponse<ProductListResponse> searchProducts(String keyword, Category category, Pageable pageable) {
         QProduct product = QProduct.product;
         QImage image = QImage.image;
         QUser user = QUser.user;
         QZzim zzim = QZzim.zzim;
+        QReview review = QReview.review;

         ...
         List<ProductListResponse> content = jpaQueryFactory
                 .select(Projections.constructor(ProductListResponse.class,
                         product.id,
                         product.title,
                         product.price,
                         user.nickname.coalesce(user.name).coalesce(""),
                         user.picture.coalesce(""),
                         image.imageUrl,
                         product.isSale.not(),
-                        zzim.id.count()
+                        zzim.id.count(),
+                        JPAExpressions
+                                .select(review.rating.avg().coalesce(0.0))
+                                .from(review)
+                                .where(review.targetUserId.eq(user.id)
+                                        .and(review.productId.eq(product.id))),
+                        JPAExpressions
+                                .select(review.count())
+                                .from(review)
+                                .where(review.targetUserId.eq(user.id)
+                                        .and(review.productId.eq(product.id)))
                 ))

Also applies to: 257-261


159-167: 상세 조회의 평점 산출 기준이 목록과 불일치합니다.

getProductDetail은 판매자 기준(상품 무관) 평균, 목록은 판매자+상품 기준 평균/개수입니다. 의도 확인 후 한쪽으로 정합화하세요. ‘sellerRating’ 네이밍을 유지하려면 보통 판매자 전체 평균이 자연스럽습니다.
[ raise_major_issue ][ request_verification ]

상품 기준으로 맞출 경우:

-                                .where(review.targetUserId.eq(user.id)),
+                                .where(review.targetUserId.eq(user.id)
+                                        .and(review.productId.eq(product.id))),

224-232: 서브쿼리 표현 통일 및(선택) 반올림 적용 제안.

  • 한 곳은 user.id, 다른 곳은 product.user.id를 사용합니다. 한 가지로 통일하면 가독성이 올라갑니다.
  • 응답 표시를 1자리로 고정하려면 DB 레벨 반올림을 적용할 수 있습니다(선택).
    [ suggest_optional_refactor ]

예(표준 ROUND, Dialect 호환성 확인 필요):

-                        JPAExpressions
-                                .select(review.rating.avg().coalesce(0.0))
+                        com.querydsl.core.types.dsl.Expressions.numberTemplate(
+                                Double.class, "ROUND({0}, 1)",
+                                JPAExpressions.select(review.rating.avg().coalesce(0.0))
+                                              .from(review)
+                                              .where(review.targetUserId.eq(user.id)
+                                                      .and(review.productId.eq(product.id))))
-                                .from(review)
-                                .where(review.targetUserId.eq(user.id).and(review.productId.eq(product.id))),
+                        ,

214-243: 성능 관점: 리뷰 서브쿼리 인덱스 권장.

행당 두 번의 상관 서브쿼리가 실행됩니다. 리뷰 테이블에 (target_user_id, product_id) 복합 인덱스를 추가하면 리스트 조회가 크게 빨라질 수 있습니다.
[ offer_operational_advice ]

  • 제안 인덱스: CREATE INDEX idx_review_target_product ON review(target_user_id, product_id);
  • 대안: 동일 서브쿼리를 재사용하는 조인+그룹 방식(ORM에서 복잡)이나 캐시.

40-53: 정렬: 도메인 필드 기반만 지원. 신규 가상 필드 정렬 의도가 있다면 매핑이 필요합니다.

getOrderSpecifiersproduct 엔티티 속성만 처리합니다. sellerRating/reviewsCount 정렬 요구가 있다면 파라미터 매핑(스위치)로 서브쿼리/수식 정렬을 별도로 구현해야 합니다.
[ request_verification ][ suggest_optional_refactor ]


214-232: 레그레션 점검 자동화 제안: ProductListResponse 투영 사용처 전체 검사.

서명 변경에 민감한 영역이라 사용처를 스캔해 두는 편이 안전합니다.
[ request_verification ]

#!/bin/bash
# ProductListResponse 프로젝션/사용처 확인
rg -n -C2 -P --type=java 'Projections\.constructor\s*\(\s*ProductListResponse\.class'
rg -n -C2 -P --type=java 'new\s+QProductListResponse\s*\(' || true

Also applies to: 277-287, 66-76

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kon28289 kon28289 merged commit 79f6695 into dev Sep 21, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants