Conversation
WalkthroughgetNearbySpotsWithAudioGuides에서 각 스팟마다 수행하던 getSpotByContentId 호출과 상세 조회 로직을 제거하고, SpotResponse의 필드만으로 NearbyAudioSpotResponse를 바로 구성하도록 내부 매핑을 단순화했습니다. 퍼블릭 API 시그니처 변경은 없습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant S as SpotServiceImpl
participant R as SpotRepository
participant D as DetailService (이전)
rect rgb(245,245,255)
note over C,S: 이전 흐름 (per-spot 상세 조회 포함)
C->>S: getNearbySpotsWithAudioGuides()
S->>R: findNearbySpots(...)
R-->>S: List<SpotResponse>
loop for each spot
S->>D: getSpotByContentId(contentId)
D-->>S: SpotDetailResponse
S-->>C: (누적) NearbyAudioSpotResponse 항목 생성
end
end
sequenceDiagram
autonumber
participant C as Client
participant S as SpotServiceImpl
participant R as SpotRepository
rect rgb(240,255,240)
note over C,S: 변경 후 흐름 (직접 매핑)
C->>S: getNearbySpotsWithAudioGuides()
S->>R: findNearbySpots(...)
R-->>S: List<SpotResponse>
S-->>C: SpotResponse만으로 NearbyAudioSpotResponse 리스트 구성
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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. Comment |
Test Results0 tests 0 ✅ 0s ⏱️ Results for commit 5c68d04. |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/yfive/gbjs/domain/spot/service/SpotServiceImpl.java (1)
518-520: N+1 내부 쿼리: existsByContentId 반복 호출 제거 필요getNearbySpotsWithAudioGuides에서 이미 배치 조회(audioGuideRepository.findContentIdsWithAudioGuidesByContentIdIn)를 수행(SpotServiceImpl.java:436-439)하는데도 fetchLocationBasedSpots → mapJsonNodeToSpotResponse에서 각 item마다 audioGuideRepository.existsByContentId(...)를 호출해 N+1 쿼리가 발생합니다 (exists 호출 위치: SpotServiceImpl.java:152, 518-520). fetchLocationBasedSpots 호출부(라인 ~428-431)와 정의(라인 ~458-492), mapJsonNodeToSpotResponse 정의(라인 ~502-520)를 확인해 조치하세요.
권장: ttsExist 계산을 옵셔널 플래그로 만들고, getNearbySpotsWithAudioGuides에서는 조회를 비활성화(또는 contentIds 세트를 전달해 메모리 체크)하여 내부 쿼리를 제거.
- private List<SpotResponse> fetchLocationBasedSpots( - Double latitude, Double longitude, String radius) { + private List<SpotResponse> fetchLocationBasedSpots( + Double latitude, Double longitude, String radius) { + // 기존 시그니처는 보존하고, 내부에서 includeTtsExist=false를 전달하는 오버로드 추가를 아래에 제안합니다.오버로드 및 플래그 전파:
@@ public List<NearbyAudioSpotResponse> getNearbySpotsWithAudioGuides( Double latitude, Double longitude) { - List<SpotResponse> allSpots = fetchLocationBasedSpots(latitude, longitude, "10000"); + List<SpotResponse> allSpots = fetchLocationBasedSpots(latitude, longitude, "10000", false); @@+ // 필요 시 ttsExist 계산 포함 여부를 제어하는 오버로드 + private List<SpotResponse> fetchLocationBasedSpots( + Double latitude, Double longitude, String radius, boolean includeTtsExist) { + String endpoint = "/locationBasedList2"; + UriComponentsBuilder uriBuilder = + UriComponentsBuilder.fromUriString(spotApiUrl + endpoint) + .queryParam("serviceKey", serviceKey) + .queryParam("numOfRows", 1000) + .queryParam("pageNo", 1) + .queryParam("MobileOS", "WEB") + .queryParam("MobileApp", "gbjs") + .queryParam("_type", "JSON") + .queryParam("arrange", "O") + .queryParam("mapX", longitude) + .queryParam("mapY", latitude) + .queryParam("radius", radius); + + String response = + restClient.get().uri(uriBuilder.build(true).toUri()).retrieve().body(String.class); + + validateApiResponse(response); + + try { + JsonNode root = objectMapper.readTree(response); + JsonNode items = root.path("response").path("body").path("items").path("item"); + + List<SpotResponse> spotResponses = new ArrayList<>(); + for (JsonNode item : items) { + JsonNode imageNode = item.get("firstimage"); + if (imageNode == null || imageNode.isNull() || imageNode.asText().isEmpty()) { + continue; + } + SpotResponse spotResponse = mapJsonNodeToSpotResponse(item, latitude, longitude, includeTtsExist); + spotResponses.add(spotResponse); + } + return spotResponses; + } catch (Exception e) { + log.error("위치 기반 관광지 파싱 실패", e); + throw new CustomException(SpotErrorStatus.SPOT_API_ERROR); + } + }기존 호출자는 그대로 사용 가능하게 기존 메서드는 오버로드 호출:
private List<SpotResponse> fetchLocationBasedSpots( Double latitude, Double longitude, String radius) { - String endpoint = "/locationBasedList2"; - ... - // (기존 본문 전체) + return fetchLocationBasedSpots(latitude, longitude, radius, true); }mapJsonNodeToSpotResponse를 플래그로 조건부 조회:
- private SpotResponse mapJsonNodeToSpotResponse(JsonNode item, Double latitude, Double longitude) + private SpotResponse mapJsonNodeToSpotResponse( + JsonNode item, Double latitude, Double longitude, boolean includeTtsExist) throws com.fasterxml.jackson.core.JsonProcessingException { SpotResponse spotResponse = objectMapper.treeToValue(item, SpotResponse.class); @@ - boolean ttsExist = audioGuideRepository.existsByContentId(item.get("contentid").asLong()); - spotResponse.setTtsExist(ttsExist); + if (includeTtsExist) { + boolean ttsExist = audioGuideRepository.existsByContentId(item.get("contentid").asLong()); + spotResponse.setTtsExist(ttsExist); + } return spotResponse;수정 위치 참고: src/main/java/com/yfive/gbjs/domain/spot/service/SpotServiceImpl.java (getNearbySpotsWithAudioGuides 호출: ~라인 428-431, 배치 조회: ~라인 436-439, fetchLocationBasedSpots 정의: ~라인 458-492, mapJsonNodeToSpotResponse: ~라인 502-520).
🧹 Nitpick comments (5)
src/main/java/com/yfive/gbjs/domain/spot/service/SpotServiceImpl.java (5)
441-447: 정렬 전에 필터링하여 불필요한 비교 줄이기오디오가이드가 있는 스팟만 먼저 걸러낸 뒤 정렬하면 비교 횟수/메모리 사용을 줄일 수 있습니다(최대 1000건 기준 체감은 작지만 안전한 미세 최적화).
- return allSpots.stream() - .sorted( - Comparator.comparing( - SpotResponse::getDistance, Comparator.nullsLast(Double::compareTo))) - .filter( - spot -> contentIdsWithAudioGuides.contains(spot.getSpotId())) + return allSpots.stream() + .filter(spot -> contentIdsWithAudioGuides.contains(spot.getSpotId())) + .sorted(Comparator.comparing(SpotResponse::getDistance, Comparator.nullsLast(Double::compareTo))) .limit(5) .map(
433-439: IN 절 축소: spotId 중복 제거 및 null 방지중복 spotId가 존재하면 불필요하게 큰 IN 쿼리가 생성됩니다. distinct(+null 필터)를 권장합니다.
- List<Long> allSpotIds = - allSpots.stream().map(SpotResponse::getSpotId).collect(Collectors.toList()); + List<Long> allSpotIds = allSpots.stream() + .map(SpotResponse::getSpotId) + .filter(java.util.Objects::nonNull) + .distinct() + .toList();distinct 적용 후 리포지토리 메서드가 빈 리스트 입력을 안전하게 처리하는지 확인 부탁드립니다.
430-436: 빈 결과 빠른 반환으로 불필요한 처리 회피후속 쿼리/정렬을 건너뛸 수 있습니다.
public List<NearbyAudioSpotResponse> getNearbySpotsWithAudioGuides( Double latitude, Double longitude) { - List<SpotResponse> allSpots = fetchLocationBasedSpots(latitude, longitude, "10000"); + List<SpotResponse> allSpots = fetchLocationBasedSpots(latitude, longitude, "10000"); + if (allSpots.isEmpty()) { + return List.of(); + }
358-363: 페이지네이션에서 항목당 외부 API 2회 호출(상세+분류)로 인한 비용
paginateSpotResponses는 각 항목마다getSpotByContentId를 호출하고, 내부에서 다시fetchSpotType까지 호출되어 외부 API가 항목당 최대 2회 발생합니다. 페이지 크기에 비례해 지연이 커집니다.
- 단기: 유형(type) 표시가 목록 필수 요구가 아니라면 지연 로딩 또는 비동기 보강을 고려.
- 중기:
contentTypeId+cat1/2/3조합별로fetchSpotType결과를 캐시(예:Map<Triple, String>), 또는 고정 사전 테이블로 대체.필요 시 캐시 스케치 제공 가능합니다.
496-499: 예외 로깅 누락파싱 실패 시 로그가 없어 트러블슈팅이 어렵습니다. 다른 메서드들처럼 에러 로그를 남겨 주세요.
- } catch (Exception e) { - throw new CustomException(SpotErrorStatus.SPOT_API_ERROR); + } catch (Exception e) { + log.error("위치 기반 관광지 파싱 실패", e); + throw new CustomException(SpotErrorStatus.SPOT_API_ERROR); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/yfive/gbjs/domain/spot/service/SpotServiceImpl.java(1 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: Build and Test
🔇 Additional comments (1)
src/main/java/com/yfive/gbjs/domain/spot/service/SpotServiceImpl.java (1)
449-454: 확인: SpotResponse.contentid 매핑은 OK — NearbyAudioSpotResponse에 distance/type 미포함 (검증 필요)
- SpotResponse.spotId는 @JsonProperty("contentid")로 매핑되어 있어 spot.getSpotId()를 contentId로 사용하는 것은 적절합니다. (src/main/java/com/yfive/gbjs/domain/spot/dto/response/SpotResponse.java)
- NearbyAudioSpotResponse는 현재 contentId, title, imageUrl만 선언되어 있어 distance/type 필드가 없습니다. (src/main/java/com/yfive/gbjs/domain/spot/dto/response/NearbyAudioSpotResponse.java)
- 계약상 distance(또는 type)가 필요하면 DTO에 필드 추가 후 SpotServiceImpl 매핑에 아래처럼 추가하십시오:
spot -> NearbyAudioSpotResponse.builder() .contentId(spot.getSpotId()) .title(spot.getTitle()) .imageUrl(spot.getImageUrl()) + .distance(spot.getDistance()) // DTO에 필드가 있을 때만 .build())
불필요한 외부 api호출 삭제 -> 외부API, 내부 쿼리 1번씩 호출로 속도 개선
Summary by CodeRabbit