-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 자신이 응원한 가게 조회 API 구현 #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough자신이 응원한 가게 목록을 조회하는 신규 API(GET /api/shops/cheered-member)를 추가했다. 컨트롤러·서비스·레포지토리와 응답 DTO(단건/목록)를 도입했고, 응원 시점 DESC 정렬 및 가게별 응원 수 집계를 구현했으며 관련 단위·문서 테스트를 추가했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Controller as StoreController
participant Service as StoreService
participant StoreRepo as StoreRepository
participant CheerRepo as CheerRepository
Client->>Controller: GET /api/shops/cheered-member (Authorization)
Controller->>Service: getStoresByCheeredMember(memberId)
Service->>StoreRepo: findAllByCheeredMemberId(memberId)
StoreRepo-->>Service: List<Store>
loop for each Store
Service->>CheerRepo: countByStore(store)
CheerRepo-->>Service: int
end
Service-->>Controller: StoresInMemberResponse
Controller-->>Client: 200 OK + JSON
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Poem
📜 Recent review detailsConfiguration used: .coderabbit.yaml 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (7)
src/test/java/eatda/fixture/CheerGenerator.java (1)
39-43: 생성 로직 중복 최소화 제안 (private 팩토리 메서드로 위임)
generate(...)가generateAdmin(...)과 유사한 생성/저장 로직을 반복합니다. 테스트 픽스처라 하더라도 중복을 줄이면 변경 내구성이 좋아집니다. 아래처럼 private 메서드로 위임하는 경량 리팩터링을 고려해보세요.적용 제안(diff: 현재 변경 라인 범위 내 본문만 교체):
- public Cheer generate(Member member, Store store, LocalDateTime createdAt) { - Cheer cheer = new Cheer(member, store, DEFAULT_DESCRIPTION, new ImageKey(DEFAULT_IMAGE_KEY), false); - DomainUtils.setCreatedAt(cheer, createdAt); - return cheerRepository.save(cheer); - } + public Cheer generate(Member member, Store store, LocalDateTime createdAt) { + return saveWithCreatedAt(member, store, false, DEFAULT_IMAGE_KEY, createdAt); + }추가(선택): 파일 내 임의 위치에 공통 팩토리 메서드 도입
private Cheer saveWithCreatedAt(Member member, Store store, boolean admin, String imageKey, LocalDateTime createdAt) { Cheer cheer = new Cheer(member, store, DEFAULT_DESCRIPTION, new ImageKey(imageKey), admin); DomainUtils.setCreatedAt(cheer, createdAt); return cheerRepository.save(cheer); }참고: 동일 패턴으로
generateAdmin(...)도 위 메서드로 위임 가능.src/main/java/eatda/repository/cheer/CheerRepository.java (1)
34-35: count 반환 타입 long 권장 + N+1 회피용 집계 쿼리 고려
- 반환 타입:
countBy*는 개념적으로 long이 더 안전합니다. 극단적 케이스에서 int 오버플로우 우려를 배제할 수 있습니다. 서비스/DTO에서 long을 사용 중이라면 일관성도 좋아집니다.- 성능:
getStoresByCheeredMember흐름에서 스토어 목록 N개에 대해countByStore를 N번 호출하면 N+1 문제가 생깁니다. 한 번의 집계 쿼리로 가져오도록 확장하는 것을 권장합니다.반환 타입 정정 제안(diff: 현재 변경 라인 내):
-int countByStore(Store store); +long countByStore(Store store);선택 적용: N개 스토어에 대한 집계(count)를 한 번에 조회하는 메서드(프로젝션/레코드 사용)
public interface StoreCheerCount { Long getStoreId(); Long getCount(); } @Query("select c.store.id as storeId, count(c) as count from Cheer c where c.store in :stores group by c.store.id") List<StoreCheerCount> countByStores(@Param("stores") List<Store> stores);서비스에서는 조회된 스토어 ID -> count 맵을 만들어 매핑하면 N+1을 제거할 수 있습니다.
src/test/java/eatda/controller/store/StoreControllerTest.java (1)
149-175: 컨트롤러 수준에서 cheerCount 검증도 함께 수행 권장정렬만이 아니라 응답 계약의 핵심 필드인
cheerCount도 컨트롤러 테스트에서 검증하면 회귀 방지에 유리합니다. 이 케이스에서는 동일 회원이 각 스토어에 1회씩 응원했으므로 둘 다 1이어야 합니다.추가 제안(diff: 현재 블록 내 assertion 보강):
assertAll( () -> assertThat(response.stores()).hasSize(2), () -> assertThat(response.stores().get(0).id()).isEqualTo(store2.getId()), - () -> assertThat(response.stores().get(1).id()).isEqualTo(store1.getId()) + () -> assertThat(response.stores().get(0).cheerCount()).isEqualTo(1L), + () -> assertThat(response.stores().get(1).id()).isEqualTo(store1.getId()), + () -> assertThat(response.stores().get(1).cheerCount()).isEqualTo(1L) );src/main/java/eatda/service/store/StoreService.java (1)
68-75: N+1 문제 해결 및 성능 최적화 검토.현재 구현에서 각 Store마다
cheerRepository.countByStore(store)를 개별적으로 호출하여 N+1 문제가 발생할 수 있습니다.성능 최적화를 위해 다음 방법을 고려해보세요:
- 일괄 집계 쿼리 사용: CheerRepository에
Map<Long, Long> countByStores(List<Store> stores)메소드 추가- JPQL 개선: StoreRepository의 기존 쿼리에 COUNT를 포함한 통합 쿼리로 변경
예시 개선안:
@Transactional(readOnly = true) public StoresInMemberResponse getStoresByCheeredMember(long memberId) { List<Store> stores = storeRepository.findAllByCheeredMemberId(memberId); + Map<Long, Long> cheerCounts = cheerRepository.countByStores(stores); List<StoreInMemberResponse> responses = stores.stream() - .map(store -> new StoreInMemberResponse(store, cheerRepository.countByStore(store))) + .map(store -> new StoreInMemberResponse(store, cheerCounts.getOrDefault(store.getId(), 0L))) .toList(); return new StoresInMemberResponse(responses); }src/main/java/eatda/controller/store/StoreInMemberResponse.java (1)
12-20: cheerCount 파라미터 타입을 long으로 통일해 암묵적 형변환 제거현재 int → long 암묵적 확장은 문제 없지만, 저장소/서비스 계층이 long으로 확장될 가능성을 고려해 시그니처를 long으로 맞추면 캐스팅과 잠재적 오버플로우 우려를 줄일 수 있습니다.
아래처럼 생성자 시그니처만 조정해도 영향 범위가 작고 명확해집니다.
- public StoreInMemberResponse(Store store, int cheerCount) { + public StoreInMemberResponse(Store store, long cheerCount) { this( store.getId(), store.getName(), store.getAddressDistrict(), store.getAddressNeighborhood(), cheerCount ); }src/test/java/eatda/document/store/StoreDocumentTest.java (2)
231-246: 문서 요약에 정렬 기준을 명시하는 것이 좋습니다클라이언트 기대치를 명확히 하기 위해 요약에 “최신 응원순(응원 시점 DESC)”과 같은 정렬 기준을 포함하는 것을 권장합니다.
- .summary("회원이 응원한 가게 목록 조회") + .summary("회원이 응원한 가게 목록 조회 (최신 응원순 DESC)")
248-266: 빈 결과 케이스 추가 테스트 제안응원한 가게가 없을 때 빈 배열이 반환되는지 보장하는 테스트가 있으면 회귀를 방지하는 데 도움이 됩니다.
아래 테스트를 같은 Nested 클래스에 추가하는 것을 제안드립니다.
@Test void 회원이_응원한_음식점_목록을_조회() { StoresInMemberResponse response = new StoresInMemberResponse(List.of( new StoreInMemberResponse(1L, "농민백암순대", "강남구", "대치동", 5L), new StoreInMemberResponse(2L, "홍콩반점", "강남구", "역삼동", 1L) )); doReturn(response).when(storeService).getStoresByCheeredMember(anyLong()); @@ .then().statusCode(200); } + + @Test + void 회원이_응원한_음식점_목록이_비어있을_때_빈_배열_반환() { + StoresInMemberResponse response = new StoresInMemberResponse(List.of()); + doReturn(response).when(storeService).getStoresByCheeredMember(anyLong()); + + var document = document("store/get-by-cheered-member", 200) + .request(requestDocument) + .response(responseDocument) + .build(); + + given(document) + .contentType(ContentType.JSON) + .header(HttpHeaders.AUTHORIZATION, accessToken()) + .when().get("/api/shops/cheered-member") + .then().statusCode(200); + }원하시면 제가 PR에 바로 반영 가능한 패치를 준비해드리겠습니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
src/main/java/eatda/controller/store/StoreController.java(1 hunks)src/main/java/eatda/controller/store/StoreInMemberResponse.java(1 hunks)src/main/java/eatda/controller/store/StoresInMemberResponse.java(1 hunks)src/main/java/eatda/repository/cheer/CheerRepository.java(1 hunks)src/main/java/eatda/repository/store/StoreRepository.java(2 hunks)src/main/java/eatda/service/store/StoreService.java(3 hunks)src/test/java/eatda/controller/BaseControllerTest.java(1 hunks)src/test/java/eatda/controller/store/StoreControllerTest.java(1 hunks)src/test/java/eatda/document/store/StoreDocumentTest.java(2 hunks)src/test/java/eatda/fixture/CheerGenerator.java(1 hunks)src/test/java/eatda/repository/store/StoreRepositoryTest.java(1 hunks)src/test/java/eatda/service/store/StoreServiceTest.java(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/test/java/eatda/fixture/CheerGenerator.java (1)
src/test/java/eatda/util/DomainUtils.java (1)
DomainUtils(7-21)
src/test/java/eatda/controller/store/StoreControllerTest.java (2)
src/test/java/eatda/document/store/StoreDocumentTest.java (5)
Nested(39-96)Nested(98-169)Nested(171-226)Nested(228-284)Nested(286-350)src/test/java/eatda/service/store/StoreServiceTest.java (4)
Nested(27-55)Nested(57-155)Nested(157-191)Nested(193-230)
src/test/java/eatda/service/store/StoreServiceTest.java (1)
src/test/java/eatda/document/store/StoreDocumentTest.java (5)
Nested(39-96)Nested(98-169)Nested(171-226)Nested(228-284)Nested(286-350)
🔇 Additional comments (14)
src/test/java/eatda/controller/BaseControllerTest.java (1)
136-138: 멤버 지정 액세스 토큰 헬퍼 추가, 테스트 편의성 향상 👍특정 멤버 컨텍스트에서 인증이 필요한 컨트롤러 테스트를 간결하게 작성할 수 있어 재사용성과 가독성이 좋아졌습니다.
src/main/java/eatda/controller/store/StoreController.java (1)
45-49: 신규 엔드포인트 추가 LGTM (GET /api/shops/cheered-member)
LoginMember리졸버를 통해 인증 컨텍스트에서 멤버 ID를 추출하고 서비스로 위임하는 흐름이 간결하고 일관적입니다. 응답을ResponseEntity.ok로 감싸는 처리도 기존 컨트롤러 스타일과 맞습니다.src/main/java/eatda/controller/store/StoresInMemberResponse.java (1)
5-6: 코드 구조가 간결하고 명확합니다.Record를 사용한 불변 DTO 구현이 적절하며, 명명 규칙도 일관성 있게 작성되었습니다.
src/main/java/eatda/repository/store/StoreRepository.java (1)
11-11: 필요한 import 추가 확인.@query 어노테이션 사용을 위한 import가 적절히 추가되었습니다.
src/main/java/eatda/service/store/StoreService.java (2)
7-7: DTO import 추가 확인.새로운 기능에 필요한 DTO import가 적절히 추가되었습니다.
Also applies to: 10-10
23-23: @transactional import 추가 확인.읽기 전용 트랜잭션 설정을 위한 import가 적절히 추가되었습니다.
src/test/java/eatda/service/store/StoreServiceTest.java (2)
9-9: 테스트에 필요한 import 추가 확인.새로운 기능 테스트를 위한 DTO와 District enum import가 적절히 추가되었습니다.
Also applies to: 11-11
193-230: 테스트 케이스 구성이 우수합니다.테스트가 다음 시나리오를 잘 검증하고 있습니다:
- 여러 사용자의 응원이 섞인 상황에서 특정 사용자의 응원만 필터링
- 시간 순서에 따른 정렬 (최근 응원 순)
- 각 가게별 응원 수 집계의 정확성
- 응원 내역이 없는 경우의 빈 목록 반환
테스트 데이터 설정과 검증 로직이 명확하게 작성되었습니다.
src/test/java/eatda/repository/store/StoreRepositoryTest.java (2)
19-36: 정렬 순서 테스트 로직이 정확합니다.
startAt.plusHours(1)로 응원한 store3이 첫 번째로,startAt에 응원한 store1이 두 번째로 나오는 것을 검증하여ORDER BY c.createdAt DESC의 동작을 올바르게 확인하고 있습니다.
38-46: 경계 조건 테스트가 적절합니다.응원 내역이 없는 사용자에 대해 빈 목록이 반환되는지 확인하는 테스트가 잘 작성되었습니다.
src/main/java/eatda/controller/store/StoreInMemberResponse.java (1)
5-11: DTO 레코드 정의 적절 — 필드 구성 및 명세 일관적입니다id/name/주소/응원수로 최소한의 응답 모델 잘 구성되어 있고, record 채택으로 불변성·직렬화에도 이점이 있습니다.
src/test/java/eatda/document/store/StoreDocumentTest.java (3)
15-15: 신규 DTO import 추가 적절문서 테스트에서 사용하는 응답 타입을 명시적으로 import하여 가독성이 좋습니다.
Also applies to: 18-18
248-266: 성공 케이스 테스트 좋습니다 — 응답 스키마 문서화도 적절성공 시나리오에서 Authorization 헤더 포함, 문서 스니펫 필드 지정, 예시 데이터 모두 적절합니다.
268-283: 실패 케이스 파라미터화 구성 적절권한 및 멤버 식별 관련 에러 코드들을 포괄적으로 검증하고 있으며, 성공 케이스와 동일한 문서 ID를 사용해 스니펫 일관성도 유지됩니다.
| @Query(""" | ||
| SELECT s FROM Store s | ||
| JOIN Cheer c ON s.id = c.store.id | ||
| WHERE c.member.id = :memberId | ||
| ORDER BY c.createdAt DESC | ||
| """) | ||
| List<Store> findAllByCheeredMemberId(long memberId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
JPQL 쿼리 성능 및 정렬 순서 검토 필요.
쿼리 구조는 올바르게 작성되었으나 몇 가지 확인이 필요합니다:
- JOIN 성능: Store와 Cheer 테이블 간의 JOIN이 인덱스를 활용할 수 있는지 확인
- 정렬 기준:
ORDER BY c.createdAt DESC가 "최근에 응원한 순서"를 의미하는지 비즈니스 요구사항과 일치하는지 검증
다음 스크립트로 쿼리 성능과 인덱스 사용을 확인해보세요:
🏁 Script executed:
#!/bin/bash
# 설명: Cheer 테이블의 인덱스 구성과 관련 쿼리 패턴을 확인합니다.
# Cheer 엔티티에서 member와 store 필드의 인덱스 설정 확인
fd -e java -x grep -l "class Cheer" | xargs cat | grep -A 20 -B 5 "@Entity\|@Table\|@Index"
# 유사한 JOIN 쿼리 패턴이 다른 곳에서도 사용되는지 확인
rg -A 5 -B 5 "JOIN.*Cheer.*ON" --type javaLength of output: 2626
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== locating Cheer class ==="
rg -n --hidden --glob '!**/node_modules/**' "class Cheer" --type java -S || true
echo
echo "=== printing Cheer class file(s) head ==="
rg -n --hidden --glob '!**/node_modules/**' "class Cheer" --type java -S | cut -d: -f1 | uniq | while read -r f; do
echo "=== file: $f ==="
sed -n '1,200p' "$f"
done
echo
echo "=== @Table with indexes (anywhere) ==="
rg -n "@Table\s*\(.*indexes" -S || true
echo
echo "=== search for 'cheer' in migration/resources and SQL files ==="
rg -n --hidden "cheer" -S migrations resources src/main/resources || true
echo
echo "=== search for CREATE INDEX in repo ==="
rg -n --hidden -S "create (unique )?index" || trueLength of output: 53557
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== StoreRepository.java ==="
sed -n '1,240p' src/main/java/eatda/repository/store/StoreRepository.java || true
echo
echo "=== V1__init.sql (migration) ==="
sed -n '1,240p' src/main/resources/db/migration/V1__init.sql || true
echo
echo "=== V5__add_cheer_tag_table.sql (migration) ==="
sed -n '1,240p' src/main/resources/db/migration/V5__add_cheer_tag_table.sql || true
echo
echo "=== AuditingEntity ==="
rg -n --hidden --glob '!**/node_modules/**' "class AuditingEntity" --type java -S || true
rg -n "class AuditingEntity" -S || true
sed -n '1,240p' src/main/java/eatda/domain/AuditingEntity.java || trueLength of output: 4501
쿼리 성능 및 정렬 검토 — 인덱스 추가 및 JPQL 개선 필요
검증 요약: 마이그레이션(src/main/resources/db/migration/V1__init.sql)과 엔티티(src/main/java/eatda/domain/cheer/Cheer.java)에서 cheer.member_id / cheer.store_id / cheer.created_at에 대한 명시적 인덱스를 찾을 수 없습니다. ORDER BY c.createdAt DESC는 테스트·비즈니스 요구(최근 응원 순)와 일치합니다. 성능과 중복 반환 방지를 위해 아래 변경을 권고합니다.
수정이 필요한 위치:
- src/main/resources/db/migration/V1__init.sql (또는 새로운 마이그레이션 파일) — cheer 테이블 인덱스 추가
- src/main/java/eatda/domain/cheer/Cheer.java — 엔티티 레벨 @table(indexes=...) 추가 검토(선택)
- src/main/java/eatda/repository/store/StoreRepository.java — findAllByCheeredMemberId JPQL 개선 (중복 제거)
권고 변경 예시:
- Repository JPQL (중복 제거 및 가독성)
@Query("""
SELECT DISTINCT c.store FROM Cheer c
WHERE c.member.id = :memberId
ORDER BY c.createdAt DESC
""")
List<Store> findAllByCheeredMemberId(long memberId);
또는 기존 SELECT s 유지 시 DISTINCT 추가:
@Query("""
SELECT DISTINCT s FROM Store s
JOIN Cheer c ON s.id = c.store.id
WHERE c.member.id = :memberId
ORDER BY c.createdAt DESC
""")
List<Store> findAllByCheeredMemberId(long memberId);
- DB 마이그레이션 예시 (새 파일로 추가)
CREATE INDEX idx_cheer_member_created_at ON cheer (member_id, created_at);
CREATE INDEX idx_cheer_store_id ON cheer (store_id);
짧은 권장 절차: (1) JPQL에 DISTINCT 적용 또는 Cheer 기반 조회로 변경, (2) 새 마이그레이션으로 인덱스 추가(특히 member_id, created_at의 복합 인덱스), (3) 운영 DB에서 EXPLAIN으로 인덱스 사용 여부 확인 후 배포.
🤖 Prompt for AI Agents
In src/main/java/eatda/repository/store/StoreRepository.java around lines 27-33,
the JPQL can return duplicate Stores and lacks DISTINCT; update the query to
select DISTINCT (either SELECT DISTINCT c.store FROM Cheer c ... ORDER BY
c.createdAt DESC or add DISTINCT to the existing SELECT s ... JOIN ...) to
prevent duplicates and ensure ordering by cheer.createdAt; additionally add a
new DB migration (src/main/resources/db/migration/V2__add_cheer_indexes.sql or
similar) that creates a composite index on cheer(member_id, created_at) and an
index on cheer(store_id), and optionally add @Table(indexes=...) to
src/main/java/eatda/domain/cheer/Cheer.java if you want entity-level index
metadata; after changes run EXPLAIN on the production DB to verify the new
indexes are used before deploying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DISTINCT를 사용하도록 반영 완료- 인덱스는 추후 도입 예정
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/main/java/eatda/repository/store/StoreRepository.java(2 hunks)src/test/java/eatda/document/cheer/CheerDocumentTest.java(1 hunks)src/test/java/eatda/document/store/StoreDocumentTest.java(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/java/eatda/document/store/StoreDocumentTest.java
- src/main/java/eatda/repository/store/StoreRepository.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/main/java/eatda/repository/store/StoreRepository.java (2)
27-31: 성능 대비: Cheer 인덱스 추가 계획 유지 권장 (member_id, created_at 복합 인덱스)
해당 쿼리는 memberId 필터 + createdAt 정렬을 수행하므로cheer(member_id, created_at)복합 인덱스가 체감 성능을 크게 개선합니다. 이전 논의에서 “인덱스는 추후 도입 예정”이라고 하셨으니, 운영 반영 전에 마이그레이션으로 추가하는 일정만 확정 부탁드립니다. 예시:
CREATE INDEX idx_cheer_member_created_at ON cheer(member_id, created_at);CREATE INDEX idx_cheer_store_id ON cheer(store_id);
27-33: 중복 Store 반환 가능 + 정렬 기준 불안정 — DISTINCT/집계 기반으로 쿼리 보완 필요
동일 멤버가 동일 가게를 여러 번 응원할 수 있는 모델이라면(현 마이그레이션/엔티티에 유니크 제약 확인 어려움), 현재 쿼리는 중복 Store를 반환합니다. 이전 코멘트에서도 지적되었던 부분으로 보이나, 본 파일에는 DISTINCT가 반영되어 있지 않습니다. 또한 DISTINCT만 추가하면 DB/JPQL 구현에 따라ORDER BY c.createdAt가 허용되지 않거나 비결정적일 수 있어 최신 응원 시점 기준 정렬을 안전하게 보장하려면 집계 기반 정렬을 권장합니다.권장(집계 기반, 단일 행/Store + 최신 응원순 보장 — Hibernate에서 일반적으로 동작):
- @Query(""" - SELECT s FROM Store s - JOIN Cheer c ON s.id = c.store.id - WHERE c.member.id = :memberId - ORDER BY c.createdAt DESC - """) - List<Store> findAllByCheeredMemberId(long memberId); + @Query(""" + SELECT s FROM Store s + JOIN Cheer c ON s.id = c.store.id + WHERE c.member.id = :memberId + GROUP BY s.id + ORDER BY MAX(c.createdAt) DESC + """) + List<Store> findAllByCheeredMemberId(@Param("memberId") long memberId);최소 변경(DISTINCT만 추가 — 중복 제거 목적, 단 DB에 따라
ORDER BY c.createdAt와의 호환성 이슈 가능):- @Query(""" - SELECT s FROM Store s + @Query(""" + SELECT DISTINCT s FROM Store s JOIN Cheer c ON s.id = c.store.id WHERE c.member.id = :memberId ORDER BY c.createdAt DESC """) - List<Store> findAllByCheeredMemberId(long memberId); + List<Store> findAllByCheeredMemberId(@Param("memberId") long memberId);참고:
- GROUP BY 방식은 Hibernate에서는 널리 쓰이지만, JPA 표준 이식성은 DB/프로바이더에 따라 차이가 있어 사전 검증이 필요합니다.
- 만약 도메인 상 “한 멤버가 한 가게를 한 번만 응원”이 보장된다면, 스키마에 (member_id, store_id) 유니크 제약을 추가하는 편이 더 확실합니다.
🧹 Nitpick comments (2)
src/main/java/eatda/repository/store/StoreRepository.java (2)
33-33: 명명된 파라미터 바인딩 안정성: @param 추가 권장
빌드 설정에-parameters가 없거나 파라미터 이름 메타데이터가 유지되지 않으면 런타임에Named parameter not bound가 발생할 수 있습니다. 안전하게@Param("memberId")를 명시하세요.- List<Store> findAllByCheeredMemberId(long memberId); + List<Store> findAllByCheeredMemberId(@Param("memberId") long memberId);
11-11: @param import 추가 필요
위 제안(@param 사용)을 적용하려면 다음 import가 필요합니다.import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param;
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/main/java/eatda/repository/store/StoreRepository.java(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (1)
src/main/java/eatda/repository/store/StoreRepository.java (1)
11-11: LGTM: @query import 추가 적절합니다.
리포지토리 메서드에 대한 JPQL 사용을 위해 필요한 의존이 정확히 추가되었습니다.
lvalentine6
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 PR도 고생하셨습니다! ⚡
역시 응원 태그는 N + 1 발생할 지점이 많은것 같군요...
| public StoresInMemberResponse getStoresByCheeredMember(long memberId) { | ||
| List<Store> stores = storeRepository.findAllByCheeredMemberId(memberId); | ||
| List<StoreInMemberResponse> responses = stores.stream() | ||
| .map(store -> new StoreInMemberResponse(store, cheerRepository.countByStore(store))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 부분이 N + 1 쿼리 패턴일것 같은데...
저희가 지금 응원할수 있는 최대가 3개이니...
크게 상관은 없을지라도 주석을 추가해두는게 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
승로님 의견 반영해서 주석 추가했습니다!
|
|
🎉 This PR is included in version 1.4.0-develop.86 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.8.0-develop.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.8.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



✨ 개요
🧾 관련 이슈
closed #165
🔍 참고 사항 (선택)
Summary by CodeRabbit