Conversation
WalkthroughDirectionType가 패키지로 이동되어 관련 import들이 갱신되었습니다. Elevator 엔티티는 latitude/longitude를 제거하고 location:String으로 대체되었고 ElevatorRepository와 FacilityService의 엘리베이터 연동이 추가되었습니다. WaybleZone 검색 로직이 근접 후보 + 메모리 유사도 방식으로 교체되고 인증 리졸버에 JWT/Authorization 처리 및 AuthErrorCase가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant FacilityService
participant ElevatorRepo as ElevatorRepository
participant DB
Client->>FacilityService: getNodeInfo(facility, routeId)
alt facility 코드 유효
FacilityService->>ElevatorRepo: findByFacility(facility)
ElevatorRepo->>DB: SELECT e FROM Elevator e WHERE e.facility = :facility
DB-->>ElevatorRepo: List<Elevator>
ElevatorRepo-->>FacilityService: elevators
FacilityService->>FacilityService: extract/trim/sort locations
FacilityService-->>Client: NodeInfo (with elevator locations)
else 오류/없음
FacilityService-->>Client: NodeInfo (elevator 빈 리스트)
end
sequenceDiagram
autonumber
participant API
participant Repo as WaybleZoneQuerySearchRepository
participant ES
API->>Repo: findSimilarWaybleZone(cond(zoneName, lat, lon))
Repo->>ES: geoDistance query (30m, size=10)
ES-->>Repo: nearby candidates
Repo->>Repo: normalize & isTextSimilar(candidate.name, cond.zoneName)
alt match found
Repo-->>API: WaybleZoneSearchResponseDto.from(doc, null)
else no match
Repo-->>API: null
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
✨ 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
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (4)
src/main/java/com/wayble/server/direction/service/TransportationService.java (2)
690-706: SUBWAY 분기에서 NPE 가능성 (route null 체크 누락)
currentEdge.getRoute()가 null일 수 있는데getRouteId()를 바로 호출합니다. 안전하게 null 체크가 필요합니다.다음 패치를 적용해 주세요:
- } else if (currentType == DirectionType.SUBWAY) { + } else if (currentType == DirectionType.SUBWAY) { try { - if (currentEdge.getStartNode() != null) { - TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); + if (currentEdge.getStartNode() != null && currentEdge.getRoute() != null) { + TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo( + currentEdge.getStartNode().getId(), + currentEdge.getRoute().getRouteId() + ); subwayInfo = new TransportationResponseDto.SubwayInfo( nodeInfo.wheelchair(), nodeInfo.elevator(), nodeInfo.accessibleRestroom() ); - } else { + } else { subwayInfo = new TransportationResponseDto.SubwayInfo( new ArrayList<>(), new ArrayList<>(), false ); } } catch (Exception e) { log.error("지하철 정보 조회 실패: {}", e.getMessage()); subwayInfo = new TransportationResponseDto.SubwayInfo( new ArrayList<>(), new ArrayList<>(), false ); } }
865-882: 환승 횟수 계산 로직 오류 — 동일 노선 연속 이용도 환승으로 계산됨현재는 같은 모드·같은 노선명인 경우에도 환승으로 카운트됩니다. 동일 노선 연속 이용은 환승이 아니어야 합니다.
다음과 같이 조건을 정리해 주세요:
- 모드가 다르면 환승 +1
- 모드는 같고 노선명이 서로 다르면 환승 +1
- 모드는 같고 노선명이 같으면 환승 +0
- if (previousMode != null) { - if (previousMode == step.mode() && - previousRouteName != null && step.routeName() != null && - !previousRouteName.equals(step.routeName())) { - transferCount++; - } else if (previousMode == step.mode() && - previousRouteName != null && step.routeName() != null && - previousRouteName.equals(step.routeName())) { - transferCount++; - } else if (previousMode != step.mode()) { - transferCount++; - } - } + if (previousMode != null) { + if (previousMode == step.mode()) { + if (previousRouteName != null && step.routeName() != null && + !previousRouteName.equals(step.routeName())) { + transferCount++; + } + } else { + transferCount++; + } + }src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (1)
593-599: Null distance 포맷팅으로 인한 NPE 발생 가능/validate 응답의 distance는 현재 null(리포지토리에서 distance 미계산)이므로,
String.format("%.3f km", dto.distance())에서 NPE가 발생합니다. null 안전 처리 필요합니다.적용 diff:
- System.out.println(" 거리: " + String.format("%.3f km", dto.distance())); + Double distance = dto.distance(); + System.out.println(" 거리: " + (distance != null ? String.format("%.3f km", distance) : "N/A"));src/main/java/com/wayble/server/direction/service/FacilityService.java (1)
69-81: 엘리베이터 조회가 화장실 코드 유효성에 종속되어 누락될 수 있음 (논리 오류)stinCd/railOprLsttCd/lnCd 중 하나라도 null이면 현재 엘리베이터 정보도 조회하지 않습니다. 엘리베이터는 내부 DB에서 facility 연관으로 조회 가능하므로, 화장실 코드 유무와 무관하게 조회해야 합니다. 아래처럼 엘리베이터 조회를 조건문 밖으로 이동해 주세요.
- if (stinCd != null && railOprLsttCd != null && lnCd != null) { - Map<String, Boolean> toiletInfo = getToiletInfo(facility); - accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); - - elevator = getElevatorInfo(facility, routeId); - } else { + if (stinCd != null && railOprLsttCd != null && lnCd != null) { + Map<String, Boolean> toiletInfo = getToiletInfo(facility); + accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); + } else { log.error("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", nodeId, stinCd, railOprLsttCd, lnCd); } + // 엘리베이터 정보는 시설 코드 유무와 무관하게 DB에서 조회 + elevator = getElevatorInfo(facility, routeId);
🧹 Nitpick comments (17)
src/main/java/com/wayble/server/direction/entity/type/DirectionType.java (1)
6-8: 운송 수단 타입과 경로 마커(TO_/FROM_WAYPOINT) 혼재 — 분리 고려DB에
EnumType.STRING으로 저장되는 엔티티 필드(Node.nodeType, Edge.edgeType)에서 실수로 마커 값이 영속될 가능성이 있습니다. 운송 수단(BUS/SUBWAY/WALK)과 경로 마커(TO_/FROM_WAYPOINT)를 별도 enum으로 분리하거나, 마커는 서비스 레이어 전용(비영속) 타입으로 관리하는 방안을 고려해주세요.src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1)
38-46: ID 수동 설정과 @GeneratedValue의 충돌 가능성
createEdge에서 ID를 외부에서 받아 빌더에 설정하지만, 엔티티는@GeneratedValue(IDENTITY)를 사용합니다. 현재는 비영속 그래프용 임시 객체로 쓰는 듯하나, 실수로 영속화될 경우 식별자 충돌/예외 가능성이 있습니다. 임시 그래프 Edge는 별도의 DTO/레코드로 분리하거나, 엔티티를 재사용하더라도 ID는 null로 두는 형태(팩토리 메서드에서 ID 제거)를 권장합니다.src/main/java/com/wayble/server/direction/service/TransportationService.java (3)
485-486: 불변 리스트에 add 가능성 — computeIfAbsent로 방어
graph.getOrDefault(currId, List.of())는 키가 없을 때 불변 리스트를 반환하므로 이후currentEdges.add(...)에서UnsupportedOperationException위험이 있습니다. 안전하게computeIfAbsent로 교체하세요.- List<Edge> currentEdges = graph.getOrDefault(curr.getId(), List.of()); + List<Edge> currentEdges = graph.computeIfAbsent(curr.getId(), k -> new ArrayList<>());
129-133: OutOfMemoryError 캐치 지양
OutOfMemoryError는 복구 불가능한Error계열입니다. 잡아서 일반 예외처럼 숨기면 장애 원인 파악이 어려워집니다. 일반 예외만 캐치하고, OOME는 상위로 전파되도록 두는 편이 좋습니다.- } catch (OutOfMemoryError e) { - log.error("Out of memory error in transportation route finding: {}", e.getMessage()); - throw new ApplicationException(PATH_NOT_FOUND); + } catch (Exception e) { + log.error("예상치 못한 예외로 경로 탐색에 실패했습니다: {}", e.getMessage()); + throw new ApplicationException(PATH_NOT_FOUND); }
400-401: 임시 WALK 엣지의 중복 ID 사용임시 도보 엣지에 고정 ID(-1, -2 등)를 반복 사용합니다. 현재는 비영속이라 문제 없지만, 실수로 영속되면 중복 키/예상치 못한 동작이 발생할 수 있습니다. ID는 null로 두거나(엔티티 재사용 시), 그래프 전용 DTO를 쓰는 방향을 권장합니다.
Also applies to: 413-414
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
21-26: 주석 보정 제안 (모드 예시)현재 주석의 예시에
START/FINISH가 포함되어 있으나, 실제Step.mode로는 사용되지 않습니다. 혼동 방지를 위해 예시를 정리해주세요.- DirectionType mode, // 예: START, WALK, SUBWAY, BUS, FINISH + DirectionType mode, // 예: WALK, SUBWAY, BUSsrc/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
3-3: 동일 패키지에 대한 와일드카드 import 제거 권장동일 패키지(
com.wayble.server.direction.entity.transportation)는 import가 필요 없습니다. 불필요한 와일드카드 import를 제거하면 코드 가독성과 정적 분석 신뢰도가 개선됩니다.적용 diff:
-import com.wayble.server.direction.entity.transportation.*;src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java (5)
138-147: 30m 고정 반경 + 첫 매치 반환은 비결정적일 수 있음(정렬 고려 권장)30m 내 후보만 가져와 텍스트 유사도 첫 매치를 반환하면, 동일 반경 내 복수 후보가 있을 때 결과가 비결정적으로 변할 수 있습니다. 근접도 기준 정렬(오름차순) 후 첫 매치를 선택하거나, 가장 높은 유사도(또는 거리+유사도 가중치)로 선택하는 방식을 고려해 주세요.
152-175: 근접 후보 조회 시 거리 정렬 추가 제안(결과 안정성 향상)ES에서 30m 후보를 가져올 때 거리 정렬을 함께 사용하면 스트림의 첫 매치가 가장 가까운 후보가 되어 결과가 안정적입니다.
적용 diff:
NativeQuery nativeQuery = NativeQuery.builder() - .withQuery(geoQuery) + .withQuery(geoQuery) + .withSort(SortOptions.of(s -> s + .geoDistance(gds -> gds + .field("address.location") + .location(GeoLocation.of(gl -> gl + .latlon(ll -> ll + .lat(cond.latitude()) + .lon(cond.longitude()) + ) + )) + .order(SortOrder.Asc) + ) + )) .withPageable(PageRequest.of(0, 10)) // 30m 이내는 보통 10개 미만 .build();
180-206: 유사도 임계치 상수화 및 재사용 제안0.7(레벤슈타인)과 0.6(자카드) 임계치는 의미 있는 매개변수입니다. 상수로 분리하면 추후 튜닝/AB 테스트가 용이합니다. 또한 검색어 정규화 결과를 외부에서 한번 계산하여 재사용하면(예: 스트림 이전) 후보가 증가해도 비용을 줄일 수 있습니다.
220-228: 레벤슈타인 유사도: 양쪽이 빈 문자열인 경우 1.0 반환 고려현재는 한쪽이라도 빈 문자열이면 0.0을 반환합니다. 양쪽 모두 빈 문자열일 때는 동등으로 간주해 1.0을 반환하는 편이 일관적입니다.
적용 diff:
- if (s1.isEmpty() || s2.isEmpty()) { - return 0.0; - } + if (s1.isEmpty() && s2.isEmpty()) { + return 1.0; + } + if (s1.isEmpty() || s2.isEmpty()) { + return 0.0; + }
259-277: 자카드 유사도: 문자 집합 기반의 한계 사항 안내문자 “집합” 기반 자카드는 문자 중복과 순서를 반영하지 못합니다. 한글 환경에서 정확도를 높이려면 bigram/trigram(shingle) 기반 자카드 또는 토크나이저 기반(예: Nori) 접근을 중장기적으로 고려할 수 있습니다.
src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (2)
83-83: 샘플 수 1000건은 CI 시간 증가 우려(태깅/프로파일 분리 권장)성능 확인 목적이라면 @tag("perf") 또는 프로파일 분리로 일반 CI에서는 축소(예: 200)하고 성능 파이프라인에서만 대량 데이터를 사용하는 운용을 권장합니다.
539-556: 응답 시간 측정은 System.nanoTime 사용 권장(정밀도 향상)HTTP 왕복 같은 상대 비교에는 nanoTime이 더 적합합니다. 큰 문제는 아니지만, 정밀도/일관성 향상을 위해 교체를 고려해 주세요.
적용 예:
long startTime = System.nanoTime(); // ... long endTime = System.nanoTime(); long responseTimeMs = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);src/main/java/com/wayble/server/direction/repository/ElevatorRepository.java (1)
12-14: 파생 쿼리 메서드로 간결화 가능현재 @query는 메서드 시그니처로 대체 가능합니다. 불필요한 @Query/@param 제거로 간결성/유지보수성 개선됩니다.
적용 diff:
- @Query("SELECT e FROM Elevator e WHERE e.facility = :facility") - List<Elevator> findByFacility(@Param("facility") Facility facility); + List<Elevator> findByFacility(Facility facility);src/main/java/com/wayble/server/direction/service/FacilityService.java (2)
73-73: getElevatorInfo의 routeId 파라미터가 사용되지 않습니다혼란을 줄이기 위해 불필요한 파라미터를 제거하는 것이 좋습니다. 호출부도 함께 수정해 주세요.
- elevator = getElevatorInfo(facility, routeId); + elevator = getElevatorInfo(facility);-private List<String> getElevatorInfo(Facility facility, Long routeId) { +private List<String> getElevatorInfo(Facility facility) {Also applies to: 142-143
103-109: KRIC API 호출에 타임아웃이 없어 블로킹 리스크 존재WebClient.block()에 타임아웃이 없어 외부 API 지연 시 무기한 대기할 수 있습니다. 운영 안전을 위해 합리적인 타임아웃을 추가하는 것을 권장합니다.
KricToiletRawResponse response = kricWebClient .get() .uri(uri) .retrieve() - .bodyToMono(KricToiletRawResponse.class) - .block(); + .bodyToMono(KricToiletRawResponse.class) + .timeout(java.time.Duration.ofSeconds(3)) + .block();
📜 Review details
Configuration used: CodeRabbit UI
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 (11)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java(1 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Edge.java(1 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Elevator.java(1 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Node.java(1 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Route.java(1 hunks)src/main/java/com/wayble/server/direction/entity/type/DirectionType.java(1 hunks)src/main/java/com/wayble/server/direction/repository/ElevatorRepository.java(1 hunks)src/main/java/com/wayble/server/direction/service/FacilityService.java(4 hunks)src/main/java/com/wayble/server/direction/service/TransportationService.java(1 hunks)src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java(3 hunks)src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (3)
src/main/java/com/wayble/server/direction/entity/DirectionType.java (1)
DirectionType(3-8)src/main/java/com/wayble/server/direction/entity/Edge.java (1)
Edge(5-11)src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (1)
Repository(11-31)
src/main/java/com/wayble/server/direction/repository/ElevatorRepository.java (2)
src/main/java/com/wayble/server/direction/entity/transportation/Lift.java (1)
Entity(6-29)src/main/java/com/wayble/server/direction/entity/transportation/Facility.java (1)
Entity(11-45)
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
src/main/java/com/wayble/server/direction/entity/DirectionType.java (1)
DirectionType(3-8)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
src/main/java/com/wayble/server/direction/entity/DirectionType.java (1)
DirectionType(3-8)
src/main/java/com/wayble/server/direction/entity/type/DirectionType.java (1)
src/main/java/com/wayble/server/direction/entity/DirectionType.java (1)
DirectionType(3-8)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
src/main/java/com/wayble/server/direction/entity/DirectionType.java (1)
DirectionType(3-8)
src/test/java/com/wayble/server/explore/WaybleZoneSearchApiIntegrationTest.java (1)
src/test/java/com/wayble/server/explore/WaybleFacilityApiIntegrationTest.java (1)
Test(179-235)
src/main/java/com/wayble/server/direction/service/FacilityService.java (5)
src/main/java/com/wayble/server/direction/entity/transportation/Lift.java (1)
Entity(6-29)src/main/java/com/wayble/server/direction/entity/transportation/Facility.java (1)
Entity(11-45)src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (1)
FacilityRepository(12-17)src/test/java/com/wayble/server/explore/WaybleFacilityApiIntegrationTest.java (1)
Test(179-235)src/main/java/com/wayble/server/explore/entity/FacilityType.java (1)
FacilityType(3-6)
src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java (1)
src/main/java/com/wayble/server/explore/service/WaybleZoneSearchService.java (1)
Service(15-49)
🔇 Additional comments (10)
src/main/java/com/wayble/server/direction/entity/type/DirectionType.java (1)
1-1: 패키지 이동 적절합니다
com.wayble.server.direction.entity→...entity.type로의 이동은 의미상 명확하고, 다른 클래스들의 import 갱신과도 일관됩니다.src/main/java/com/wayble/server/direction/entity/transportation/Node.java (1)
11-11: import 경로 변경 OK
DirectionType의 패키지 이동에 맞춘 import 갱신이 정확합니다. 그 외 엔티티 매핑 변경은 없습니다.src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1)
3-3: import 경로 변경 OK
DirectionType의 새로운 패키지를 정확히 참조하고 있습니다.src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
10-10: import 경로 변경 OK패키지 이동에 맞춘
DirectionTypeimport 갱신이 정확합니다.src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
5-5: import 경로 변경 OK
DirectionType패키지 이동에 맞춘 import 갱신이 정확합니다.src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
4-4: DirectionType import 경로 변경 LGTM패키지 이동에 따른 import 수정이 정확합니다. 엔티티 필드
routeType과@Enumerated(EnumType.STRING)조합도 문제없습니다.src/main/java/com/wayble/server/explore/repository/search/WaybleZoneQuerySearchRepository.java (3)
22-22: Set import 추가 적절자카드 유사도 계산용 Set 사용을 위한 import 추가는 적절합니다.
211-215: 정규화 로직 적절공백/특수문자를 제거하고 소문자화하는 정규화는 한글/영문 혼재 환경에서 효과적입니다.
233-254: 표준 DP 레벤슈타인 구현 LGTM시간복잡도 O(nm) 구현으로 후보 수(≤10)에서는 성능 이슈 우려 없습니다.
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)
36-36: ElevatorRepository 주입 추가는 방향이 좋습니다엘리베이터 정보를 지연 로딩 대신 전용 리포지토리로 조회하는 접근은 트랜잭션 경계 외 LazyInitializationException을 피하는 데 유효합니다.
| @Column(name = "location", nullable = false) | ||
| private String location; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
위치 스키마 변경(DB 마이그레이션/레거시 사용처 점검 필요)
latitude/longitude → location 문자열 변경에 따라:
- DB 마이그레이션(컬럼 추가/변경, 기존 lat/lon 제거 또는 비활성화)이 포함되었는지 확인 필요
- 기존 코드에서
elevator.getLatitude()/getLongitude()참조가 남아있지 않은지 점검 필요
확인을 위해 아래 스크립트로 레거시 사용처와 마이그레이션 흔적을 점검해 주세요. 결과 공유해 주시면 후속 지원하겠습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for legacy Elevator latitude/longitude usages in Java code..."
rg -nP --type=java -C2 '\.getLatitude\s*\(|\.getLongitude\s*\(' || true
rg -nP --type=java -C2 '\bElevator\b.*(latitude|longitude)' || true
rg -nP --type=java -C2 '(latitude|longitude).*Elevator' || true
echo
echo "Searching for DB migration files mentioning elevator latitude/longitude..."
fd -a -t f -i '.*(migration|migrations|flyway|liquibase|changelog).*' || true
rg -nP -C2 '(?i)elevator.*(latitude|longitude|location)' || trueLength of output: 22107
DB 스키마 변경(엘리베이터 location 전환) 마이그레이션 및 레거시 호출 점검 필요
아래 사항을 반드시 반영/확인해주세요:
- src/main/java/com/wayble/server/direction/entity/transportation/Elevator.java
latitude/longitude→location문자열 전환을 위한 Flyway/Liquibase 마이그레이션 스크립트 추가- 기존 lat/lon 컬럼에 저장된 값을
location으로 이관(migrate) - 값 이관 완료 후 lat/lon 컬럼 제거 또는 비활성화
- 기존 lat/lon 컬럼에 저장된 값을
- 코드베이스 전역에서
elevator.getLatitude()/elevator.getLongitude()호출이 남아있지 않은지 재차 검증- 첫 검색 결과 해당 호출은 발견되지 않았으나, 커스텀 유틸이나 스크립트 등 예외 경로 점검 필요
- 마이그레이션 스크립트는
src/main/resources/db/migration(또는 프로젝트 설정된 폴더)에 위치시키고, 자동 배포 파이프라인에 포함
위 작업 완료 후 결과(스크립트·검증 로그)를 공유해주시면 최종 승인 및 후속 지원을 진행하겠습니다.
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/entity/transportation/Elevator.java
around lines 17-18, the entity was changed from latitude/longitude fields to a
single location string but the DB migration and legacy-call cleanup are missing:
add a Flyway/Liquibase migration in src/main/resources/db/migration that (1)
adds the new location column if not present, (2) migrates existing
latitude/longitude values into the new location string format used by the
entity, and (3) drops or deprecates the old latitude/longitude columns after
verification; then search the entire codebase (including custom utils, scripts,
and tests) for any remaining elevator.getLatitude()/getLongitude() calls and
update them to use the new location accessor or parsing logic; ensure the
migration file is included in the automated deployment pipeline and provide
migration run logs and verification results.
[refactor] 리졸버 로직 개선, swagger에서 userId 필수 파라미터 입력인곳 제거
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/main/java/com/wayble/server/auth/resolver/CurrentUserArgumentResolver.java (1)
31-64: primitive long 지원 추가 및 숫자 검증 로직 분리 권장현재
supportsParameter메서드는Long.class만 허용해 primitivelong파라미터를 받을 수 없습니다. 또한,resolveArgument내에서 문자열 숫자 여부를 바로 검사하고 있어 가독성이 떨어집니다. 아래와 같이 개선을 제안합니다.
- supportsParameter에 primitive
long추가 (파일:src/main/java/com/wayble/server/auth/resolver/CurrentUserArgumentResolver.java)@Override public boolean supportsParameter(MethodParameter parameter) { Class<?> type = parameter.getParameterType(); return parameter.hasParameterAnnotation(CurrentUser.class) - && Long.class.equals(type); + && (Long.class.equals(type) || long.class.equals(type)); }- 숫자 검증 로직을 헬퍼 메서드로 분리
그리고private boolean isNumeric(String s) { return s != null && s.chars().allMatch(Character::isDigit); }resolveArgument내에서if (principal instanceof String s && isNumeric(s)) { … } … if (isNumeric(name)) { … }- 별도 등록 조치 불필요:
WebMvcConfig.java의addArgumentResolvers(...)로 이미CurrentUserArgumentResolver가 등록되어 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
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 (5)
src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java(1 hunks)src/main/java/com/wayble/server/auth/resolver/CurrentUserArgumentResolver.java(1 hunks)src/main/java/com/wayble/server/review/controller/ReviewController.java(2 hunks)src/main/java/com/wayble/server/user/controller/UserPlaceController.java(5 hunks)src/test/java/com/wayble/server/review/service/ReviewServiceTest.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (4)
src/main/java/com/wayble/server/user/service/UserPlaceService.java (1)
Service(27-137)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
Entity(13-47)src/main/java/com/wayble/server/user/controller/UserController.java (1)
PatchMapping(71-90)
src/test/java/com/wayble/server/review/service/ReviewServiceTest.java (2)
src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java (1)
addReviewCount(96-99)src/main/java/com/wayble/server/review/service/ReviewService.java (1)
Service(23-81)
src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java (10)
src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java (1)
Getter(7-17)src/main/java/com/wayble/server/user/exception/UserErrorCase.java (1)
Getter(7-30)src/main/java/com/wayble/server/aws/AwsErrorCase.java (1)
Getter(7-17)src/main/java/com/wayble/server/wayblezone/exception/WaybleZoneErrorCase.java (1)
Getter(7-18)src/main/java/com/wayble/server/common/MainErrorCase.java (1)
Getter(7-16)src/main/java/com/wayble/server/review/exception/ReviewErrorCase.java (1)
Getter(7-16)src/main/java/com/wayble/server/common/exception/ErrorCase.java (1)
ErrorCase(3-10)src/main/java/com/wayble/server/explore/exception/RecommendErrorCase.java (1)
Getter(7-20)src/main/java/com/wayble/server/direction/exception/WalkingErrorCase.java (1)
Getter(7-21)src/main/java/com/wayble/server/explore/exception/SearchErrorCase.java (1)
Getter(7-17)
src/main/java/com/wayble/server/auth/resolver/CurrentUserArgumentResolver.java (3)
src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java (1)
RequiredArgsConstructor(13-43)src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthentication.java (1)
JwtAuthentication(7-19)src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java (1)
getUserId(53-64)
⏰ 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: run-docker-image-on-ec2
🔇 Additional comments (5)
src/test/java/com/wayble/server/review/service/ReviewServiceTest.java (1)
69-69: Mockito verify 인수 타입 변경 적절
addReviewCount(1)로의 변경이 메서드 시그니처(long)와 호환되어 컴파일·런타임 모두 문제 없습니다. 테스트 의도도 명확합니다.src/main/java/com/wayble/server/review/controller/ReviewController.java (1)
16-16: Swagger에서 @currentuser 파라미터 숨김 처리 👍
@Parameter(hidden = true)적용으로 문서 노이즈를 줄이고, 클라이언트가 해당 값을 입력하려고 시도하는 실수를 방지합니다. 운영 관점에서도 일관성 있는 문서화 방식입니다.Also applies to: 40-40
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (2)
12-12: Swagger에서 @currentuser 파라미터 숨김 처리 일괄 적용 굿4개 엔드포인트 모두 동일하게 숨겨 문서 일관성과 보안 의도를 잘 반영했습니다.
Also applies to: 38-38, 53-53, 70-70, 90-90
70-76: 페이지 인덱스 보정 구현 확인 완료
UserPlaceService.getZonesInPlace에서 요청된 1-basedpage를int zeroBased = Math.max(0, page - 1); PageRequest.of(zeroBased, size, …)로 올바르게 보정하고 있음을 확인했습니다. 별도 수정 불필요합니다.
src/main/java/com/wayble/server/auth/resolver/CurrentUserArgumentResolver.java (1)
31-64: SecurityContext + Bearer 토큰 폴백 처리로 견고성 향상
- Long/Integer/숫자 문자열 principal 처리 +
auth.getName()숫자 처리로 범용성 확보- Authorization 헤더 Bearer 토큰 폴백 및 JWT 파싱 실패 시
ApplicationException(UNAUTHORIZED)로 일관된 오류 응답테스트/운영 모두에서 인증 정보 부재 시 명확히 401을 반환할 수 있어 좋습니다.
src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java
Outdated
Show resolved
Hide resolved
[fix] 유저 웨이블존 저장 및 조회 관련 API 수정
There was a problem hiding this comment.
Actionable comments posted: 2
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/wayble/server/user/service/UserPlaceService.java (1)
115-118: 페이지 인덱스 오프바이원 버그 (0-based를 1-based로 잘못 보정).요구사항에 따라 클라이언트는 page=0부터 전달합니다. 현재
Math.max(0, page - 1)는 page=1을 0페이지로 취급하여 2페이지 요청부터 모두 한 페이지씩 당겨집니다.바로잡기:
- int zeroBased = Math.max(0, page - 1); - - Pageable pageable = PageRequest.of(zeroBased, size, Sort.by(Sort.Direction.DESC, "id")); + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id"));추가로, 컨트롤러 레이어에서 page, size에 대해 @min(0), @min(1)/@max(...) 등의 제약을 걸어두면 입력 유효성도 보장할 수 있습니다.
🧹 Nitpick comments (11)
src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java (1)
5-8: color 길이 제약 추가 권장 (엔티티 스키마와 일치).UserPlace.color 컬럼이 length=20으로 제한되어 있습니다. DTO에도 검증을 추가하여 조기 실패시키는 것이 안전합니다.
권장 변경사항:
package com.wayble.server.user.dto; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; public record UserPlaceCreateRequestDto( @NotBlank(message = "제목은 필수입니다.") String title, - String color + @Size(max = 20, message = "color는 최대 20자까지 가능합니다.") String color ) {}src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java (1)
3-10: 컨테이너 요소 유효성 검사 및 과도한 배치 입력 방지 가드 추가 제안.
- 현재 @notempty만으로는 리스트 내부 요소의 null을 막지 못합니다. 컨테이너 요소에 @NotNull을 적용해 주세요.
- 매우 큰 입력으로 인한 부하를 방지하려면 리스트 크기에 상한을 두는 것도 고려해주세요(예: 최대 1000개).
권장 변경사항:
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; public record UserPlaceAddZonesRequestDto( @NotNull Long placeId, - @NotEmpty List<Long> waybleZoneIds + @NotEmpty @Size(max = 1000, message = "waybleZoneIds는 최대 1000개까지 허용됩니다.") + List<@NotNull Long> waybleZoneIds ) {}src/main/java/com/wayble/server/user/exception/UserErrorCase.java (1)
25-26: 중복 리소스는 409 Conflict가 더 적절합니다 (일관성).닉네임 중복(NICKNAME_DUPLICATED)이 409를 사용 중인데, PLACE_TITLE_DUPLICATED는 400입니다. 의미상 409가 적합하며, 코드베이스 내 일관성도 맞춰주세요.
권장 변경사항:
- PLACE_TITLE_DUPLICATED(400, 1016, "동일한 이름의 리스트가 이미 있습니다."); + PLACE_TITLE_DUPLICATED(409, 1016, "동일한 이름의 리스트가 이미 있습니다.");src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java (1)
5-11: UserPlaceCreateResponseDto의 message 필드 제거 및 빌더 호출 정리 권장CommonResponse가 이미 응답 메시지를 제공하므로, DTO 내부의 중복된 message 필드를 제거하는 것을 권장합니다. 이 변경을 적용할 경우, 빌더 호출에서 해당 필드 관련 메서드도 함께 삭제해야 합니다.
- src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java
• record 정의에서 String message 필드 제거- src/main/java/com/wayble/server/user/controller/UserPlaceController.java
• UserPlaceCreateResponseDto.builder() 체인에서.message("리스트가 생성되었습니다.")호출 삭제src/main/java/com/wayble/server/user/service/UserPlaceService.java (4)
60-88: 대량 추가 성능 개선: N+1 쿼리/저장을 벌크로 치환.현재 구현은
- WaybleZone 개별 조회 N회
- 매핑 존재 여부 exists 쿼리 N회
- 매핑 save N회
- WaybleZone save N회
로 쿼리가 선형 증가합니다. 입력 리스트가 커질 경우 성능 저하가 큽니다.권장 방향:
- uniqueIds에 대해 WaybleZone을 한 번에 조회(findAllById).
- 기존 매핑된 zoneId들을 IN 절로 조회해 Set으로 수집.
- 신규 대상만 골라 매핑 saveAll, WaybleZone likes를 메모리에서 증가 후 saveAll.
- place.savedCount는 신규 건수만큼 루프 혹은 addCount(n) 메서드로 누적.
예시 변경(리포지토리 보조 메서드 필요):
- public void addZonesToPlace(Long userId, Long placeId, List<Long> waybleZoneIds) { + public void addZonesToPlace(Long userId, Long placeId, List<Long> waybleZoneIds) { UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); Set<Long> uniqueIds = new LinkedHashSet<>(waybleZoneIds); - int added = 0; - for (Long zoneId : uniqueIds) { - WaybleZone zone = waybleZoneRepository.findById(zoneId) - .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); - - boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, zoneId); - if (exists) continue; - - mappingRepository.save(UserPlaceWaybleZoneMapping.builder() - .userPlace(place) - .waybleZone(zone) - .build()); - - place.increaseCount(); - zone.addLikes(1); - waybleZoneRepository.save(zone); - - added++; - } - - if (added > 0) userPlaceRepository.save(place); + // 1) 존재하는 Zone 일괄 조회 및 검증 + List<WaybleZone> zones = waybleZoneRepository.findAllById(uniqueIds); + if (zones.size() != uniqueIds.size()) { + throw new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND); + } + Map<Long, WaybleZone> zoneMap = zones.stream().collect(java.util.stream.Collectors.toMap(WaybleZone::getId, z -> z)); + + // 2) 이미 매핑된 zoneId 집합 조회 + Set<Long> existing = mappingRepository.findWaybleZoneIdsByUserPlaceIdAndWaybleZoneIdIn(placeId, uniqueIds); + + // 3) 신규 대상 선별 + List<Long> toAdd = uniqueIds.stream().filter(id -> !existing.contains(id)).toList(); + if (toAdd.isEmpty()) return; + + // 4) 매핑 벌크 저장 + List<UserPlaceWaybleZoneMapping> mappings = toAdd.stream() + .map(id -> UserPlaceWaybleZoneMapping.builder().userPlace(place).waybleZone(zoneMap.get(id)).build()) + .toList(); + mappingRepository.saveAll(mappings); + + // 5) 카운트/좋아요 업데이트 벌크 + toAdd.forEach(_id -> place.increaseCount()); + List<WaybleZone> toUpdate = toAdd.stream().map(zoneMap::get).peek(z -> z.addLikes(1)).toList(); + waybleZoneRepository.saveAll(toUpdate); }리포지토리 보조 메서드 예시(별도 파일 수정 필요):
// UserPlaceWaybleZoneMappingRepository.java @Query("select m.waybleZone.id from UserPlaceWaybleZoneMapping m where m.userPlace.id = :placeId and m.waybleZone.id in :zoneIds") Set<Long> findWaybleZoneIdsByUserPlaceIdAndWaybleZoneIdIn(@Param("placeId") Long placeId, @Param("zoneIds") Collection<Long> zoneIds);원하시면 관련 리포지토리/테스트 코드도 함께 제안드리겠습니다.
87-88: 명시적 save는 생략 가능 (영속성 컨텍스트 자동 flush).place는 이미 영속 상태이므로, 변경 감지가 트랜잭션 커밋 시 반영됩니다. 불필요한 save 호출은 제거해도 됩니다.
권장 변경사항:
- if (added > 0) userPlaceRepository.save(place); + // if (added > 0) userPlaceRepository.save(place); // 생략 가능
120-132: null 필드 직렬화 방지 또는 빈 컬렉션 사용 권장.
facilities(null)은 응답 JSON에 null로 표출될 수 있습니다(해당 DTO에 @JsonInclude 설정이 없다면). 빈 목록을 사용하는 편이 API 일관성에 유리합니다.권장 변경사항:
- .facilities(null) + .facilities(java.util.Collections.emptyList())또는 WaybleZoneListResponseDto에
@JsonInclude(JsonInclude.Include.NON_NULL)를 추가해 null 필드가 출력되지 않도록 할 수 있습니다.
146-152: 삭제 경쟁 조건 완화 제안(선택).exists → delete 순서에서 경쟁이 발생하면 존재 확인 직후 다른 트랜잭션이 삭제할 수 있습니다.
deleteBy...가 삭제 행 수를 반환하도록 하고 0이면 PLACE_MAPPING_NOT_FOUND를 던지는 방식이 더 견고합니다.가능하다면 리포지토리 메서드를
int deleteByUserPlace_IdAndWaybleZone_Id(...)로 정의하여 결과를 검사하세요.src/main/java/com/wayble/server/user/controller/UserPlaceController.java (3)
6-6: 와일드카드 import(*) 대신 명시적 import로 가독성/안정성 개선 제안빌드/IDE 설정에 따라 와일드카드가 예기치 않게 확장될 수 있어, 사용하는 DTO만 명시적으로 import하는 편이 안전합니다.
다음과 같이 변경을 제안합니다:
-import com.wayble.server.user.dto.*; +import com.wayble.server.user.dto.UserPlaceAddZonesRequestDto; +import com.wayble.server.user.dto.UserPlaceCreateRequestDto; +import com.wayble.server.user.dto.UserPlaceCreateResponseDto; +import com.wayble.server.user.dto.UserPlaceRemoveRequestDto; +import com.wayble.server.user.dto.UserPlaceSummaryDto;
28-47: 생성 API의 응답 DTO 확장과 기본 색상 처리 좋습니다. 다만 403 응답 스펙 추가 및 기본값 처리 위치는 서비스/도메인으로 이동 권장
- 설계:
UserPlaceCreateResponseDto로 풍부한 응답을 제공하는 방향이 좋습니다.- 기본값:
"GRAY"기본값 처리는 서비스/도메인(엔티티 기본값 또는 팩토리)로 이동하면 컨트롤러 중복/지식 누수를 줄일 수 있습니다. 추후 다른 진입점에서도 동일 규칙을 자동 보장합니다.- 스웨거: 다른 엔드포인트들과의 일관성을 위해 403 응답 스펙을 추가하는 것이 좋습니다.
403 응답 추가에 대한 최소 diff:
@ApiResponses({ - @ApiResponse(responseCode = "200", description = "리스트 생성 성공"), - @ApiResponse(responseCode = "400", description = "동일한 리스트명이 이미 존재") + @ApiResponse(responseCode = "200", description = "리스트 생성 성공"), + @ApiResponse(responseCode = "400", description = "동일한 리스트명이 이미 존재"), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") })
82-95: 웨이블존 추가 API: 권한(403) 응답 스펙 추가 및 요청 유효성 강화(@notempty) 제안
- 문서화: 다른 엔드포인트와 동일하게 403 응답을 추가하면 일관성이 좋아집니다.
- 유효성:
waybleZoneIds가 빈 배열일 경우 불필요한 호출을 막기 위해@NotEmpty를 권장합니다(서버 자원 보호, 예측 가능한 오류 응답).403 응답 추가 diff:
@ApiResponses({ @ApiResponse(responseCode = "200", description = "웨이블존 추가 성공"), - @ApiResponse(responseCode = "404", description = "유저/리스트/웨이블존을 찾을 수 없음") + @ApiResponse(responseCode = "404", description = "유저/리스트/웨이블존을 찾을 수 없음"), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") })요청 DTO 유효성 예시(다른 파일에 적용):
// src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java package com.wayble.server.user.dto; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; public record UserPlaceAddZonesRequestDto( @NotNull Long placeId, @NotEmpty List<Long> waybleZoneIds ) {}참고: 서비스 레벨에서 입력 리스트 중복 제거(Set 변환)도 권장합니다(동일 ID 중복 추가 방지).
📜 Review details
Configuration used: CodeRabbit UI
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 (7)
src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java(1 hunks)src/main/java/com/wayble/server/user/controller/UserPlaceController.java(4 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java(1 hunks)src/main/java/com/wayble/server/user/exception/UserErrorCase.java(1 hunks)src/main/java/com/wayble/server/user/service/UserPlaceService.java(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java (6)
src/main/java/com/wayble/server/common/response/CommonResponse.java (1)
Builder(8-42)src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java (1)
Builder(5-11)src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (2)
Builder(7-22)Builder(13-21)src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java (1)
UserPlaceRequestDto(5-9)src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java (1)
Builder(8-15)src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
Entity(13-47)
src/main/java/com/wayble/server/user/exception/UserErrorCase.java (2)
src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
Entity(13-47)
src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java (4)
src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (1)
UserPlaceRemoveRequestDto(5-8)src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java (1)
UserPlaceRequestDto(5-9)src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java (1)
Builder(8-15)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)
src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java (2)
src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java (1)
UserPlaceRequestDto(5-9)src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
Entity(13-47)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (1)
src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (1)
UserPlaceRemoveRequestDto(5-8)
🔇 Additional comments (4)
src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java (1)
5-8: 검증 완료:@Valid적용 확인됨
UserPlaceController#createPlaceList메서드 시그니처에
@RequestBody @Valid UserPlaceCreateRequestDto request
가 이미 선언되어 있어,@NotBlank기반 Bean Validation이 정상 동작합니다.src/main/java/com/wayble/server/user/controller/UserPlaceController.java (3)
10-10: @parameter import 추가 적절합니다하단의
@Parameter(hidden = true)사용을 위한 import가 정확히 반영되었습니다.
57-57: @currentuser + @parameter(hidden = true) 적용 적절합니다보안 컨텍스트로부터 userId를 주입하고 문서에서 숨기는 처리가 일관되게 적용되었습니다.
97-113: API 1-base 페이지 인덱스 처리 확인 — 변경 불필요서비스 내부에서
Math.max(0, page - 1)로 1-base API를 0-base로 보정하여PageRequest.of(zeroBased, size)를 사용하고 있으므로, 컨트롤러의 문서(page는 1부터 시작)와 기본값(defaultValue = "1")은 현재 로직과 일치합니다. 제안된 0-base 기본값 및 문구 변경은 반영할 필요가 없습니다.
| @DeleteMapping | ||
| @Operation( | ||
| summary = "장소에서 웨이블존 제거", | ||
| description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." | ||
| ) | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "제거 성공"), | ||
| @ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"), | ||
| @ApiResponse(responseCode = "403", description = "권한이 없습니다.") | ||
| }) | ||
| public CommonResponse<String> removeZoneFromPlace( | ||
| @Parameter(hidden = true) @CurrentUser Long userId, | ||
| @RequestBody @Valid UserPlaceRemoveRequestDto request | ||
| ) { | ||
| userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); | ||
| return CommonResponse.success("제거되었습니다."); | ||
| } | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
DELETE 본문(RequestBody) 사용은 호환성 이슈 소지가 큼 — 경로 변수 형식으로 변경 제안
여러 클라이언트/프록시/게이트웨이(일부 ALB, CDN, 레거시 프록시)에서 DELETE 본문을 미전달/무시하는 사례가 있습니다. 안전성을 위해 /{placeId}/zones/{waybleZoneId} 경로 변수로의 설계를 권장합니다. 또한 삭제는 200 바디 대신 204 No Content도 고려할 수 있습니다(프로젝트의 CommonResponse 규약에 맞추어 결정).
경로 변수로의 변경 예시:
- @DeleteMapping
+ @DeleteMapping("/{placeId}/zones/{waybleZoneId}")
@Operation(
summary = "장소에서 웨이블존 제거",
- description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다."
+ description = "경로 변수 placeId, waybleZoneId로 지정한 장소에서 웨이블존을 제거합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "제거 성공"),
@ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"),
@ApiResponse(responseCode = "403", description = "권한이 없습니다.")
})
- public CommonResponse<String> removeZoneFromPlace(
- @Parameter(hidden = true) @CurrentUser Long userId,
- @RequestBody @Valid UserPlaceRemoveRequestDto request
- ) {
- userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId());
+ public CommonResponse<String> removeZoneFromPlace(
+ @Parameter(hidden = true) @CurrentUser Long userId,
+ @PathVariable Long placeId,
+ @PathVariable Long waybleZoneId
+ ) {
+ userPlaceService.removeZoneFromPlace(userId, placeId, waybleZoneId);
return CommonResponse.success("제거되었습니다.");
}또한, 이 삭제 시 Issue #154의 요구사항인 savedCount 자동 감소가 서비스에서 수행되는지 확인 부탁드립니다. 아래 스크립트로 빠르게 점검할 수 있습니다.
🏁 Script executed:
#!/bin/bash
# UserPlaceService.removeZoneFromPlace 구현과 savedCount 처리 여부 점검
echo "== Locate removeZoneFromPlace implementation =="
rg -nP --type=java -C4 '\bclass\s+UserPlaceService\b|removeZoneFromPlace\s*\('
echo
echo "== savedCount 증가/감소 처리 흔적 검색 =="
rg -nP --type=java -C3 '\bsavedCount\b|\bincrementSavedCount\b|\bdecrementSavedCount\b|\bsetSavedCount\b'Length of output: 7892
DELETE 본문 대신 경로 변수 사용 권고 및 savedCount 감소 로직 추가 필요
DELETE 요청 시 RequestBody가 일부 클라이언트/프록시에서 무시될 수 있으므로, 경로 변수 방식으로 변경을 권장합니다. 또한 removeZoneFromPlace 구현에서 매핑 삭제 후 savedCount를 감소시키는 로직이 누락된 것으로 확인되었습니다.
• UserPlaceController (src/main/java/com/wayble/server/user/controller/UserPlaceController.java)
- 옵션:
- @DeleteMapping + @DeleteMapping("/{placeId}/zones/{waybleZoneId}") ... - public CommonResponse<String> removeZoneFromPlace( - @Parameter(hidden = true) @CurrentUser Long userId, - @RequestBody @Valid UserPlaceRemoveRequestDto request - ) { - userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); + public CommonResponse<String> removeZoneFromPlace( + @Parameter(hidden = true) @CurrentUser Long userId, + @PathVariable Long placeId, + @PathVariable Long waybleZoneId + ) { + userPlaceService.removeZoneFromPlace(userId, placeId, waybleZoneId);
• UserPlaceService (src/main/java/com/wayble/server/user/service/UserPlaceService.java)
- 필수: 매핑 삭제 직후
savedCount감소 및 엔티티 저장 로직 추가public void removeZoneFromPlace(Long userId, Long placeId, Long waybleZoneId) { UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); // ... 매핑 존재 여부 검증 및 매핑 조회 -
mappingRepository.delete(mappingEntity);
-
mappingRepository.delete(mappingEntity); -
// Issue #154: savedCount 감소 처리 -
place.decreaseCount(); -
}
userPlaceRepository.save(place);
src/main/java/com/wayble/server/user/service/UserPlaceService.java
Outdated
Show resolved
Hide resolved
[fix] 페이징 관련 오프바이원 버그 수정 + 페이징 제약 및 검증 로직 개선
[fix] 하나의 웨이블존에 여러개의 저장한 리스트를 추가할 수 있도록 로직 변경
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (1)
106-122: DELETE의 RequestBody 사용은 호환성 이슈 소지 — 경로 변수로 변경 권장여러 클라이언트/프록시에서 DELETE 본문이 미전달/무시될 수 있습니다. 경로 변수 사용으로 안전성을 높이세요. (이 코멘트는 이전 리뷰와 동일한 맥락입니다.)
권장 변경:
- @DeleteMapping("/zones") + @DeleteMapping("/{placeId}/zones/{waybleZoneId}") @Operation( - summary = "내가 저장한 리스트에서 웨이블존 제거", - description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." + summary = "내가 저장한 리스트에서 웨이블존 제거", + description = "경로 변수 placeId, waybleZoneId로 지정한 장소에서 웨이블존을 제거합니다." ) @@ - public CommonResponse<String> removeZoneFromPlace( - @Parameter(hidden = true) @CurrentUser Long userId, - @RequestBody @Valid UserPlaceRemoveRequestDto request - ) { - userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); + public CommonResponse<String> removeZoneFromPlace( + @Parameter(hidden = true) @CurrentUser Long userId, + @PathVariable Long placeId, + @PathVariable Long waybleZoneId + ) { + userPlaceService.removeZoneFromPlace(userId, placeId, waybleZoneId); return CommonResponse.success("성공적으로 제거되었습니다."); }추가로, 204 No Content 사용 여부는 프로젝트의 CommonResponse 규약에 맞춰 검토 부탁드립니다.
🧹 Nitpick comments (5)
src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java (1)
7-10: ID 유효성 보강 제안: @positive 및 리스트 크기 제한 추가 권장
- 현재 @notempty와 컨테이너 요소 @NotNull은 적절합니다. 다만 0/음수 ID를 차단하기 위해 @positive 추가, 과도한 대량 요청 방지를 위해 @SiZe(max=...) 제한을 권장합니다.
- 서비스 레벨에서 중복 제거(LinkedHashSet)가 있지만, DTO 레벨에서도 기본적인 제약을 주면 방어력이 높아집니다.
적용 예시:
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; import java.util.List; public record UserPlaceAddZonesRequestDto( - @NotEmpty List<@NotNull Long> placeIds, - @NotNull Long waybleZoneId + @NotEmpty @Size(max = 50) List<@NotNull @Positive Long> placeIds, + @NotNull @Positive Long waybleZoneId ) {}
- max 값(예: 50)은 요구사항/운영 한도에 맞게 결정 바랍니다.
src/main/java/com/wayble/server/user/service/UserPlaceService.java (1)
83-85: save 호출 N회 → 배치 저장으로 최소화 권장반복문 내 userPlaceRepository.save(place) 호출은 N회 DB 왕복을 유발합니다. 변경된 place들을 수집해 마지막에 saveAll(placeList) 호출로 줄이는 것을 고려해 주세요.
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (3)
38-54: 정규화 로직의 중복은 서비스로 집약 권장 (응답 DTO를 서비스에서 생성)컨트롤러에서 title/color 정규화를 다시 수행하면 서비스 로직과의 불일치/드리프트가 생깁니다. 서비스가 UserPlaceCreateResponseDto를 반환하게 하여 단일 진실 공급원으로 유지하세요.
권장 변경(컨트롤러):
- public CommonResponse<UserPlaceCreateResponseDto> createPlaceList( - @Parameter(hidden = true) @CurrentUser Long userId, - @RequestBody @Valid UserPlaceCreateRequestDto request - ) { - Long placeId = userPlaceService.createPlaceList(userId, request); - String normalizedTitle = request.title().trim(); - String normalizedColor = (request.color() == null || request.color().isBlank()) - ? "GRAY" - : request.color().trim().toUpperCase(); - return CommonResponse.success( - UserPlaceCreateResponseDto.builder() - .placeId(placeId) - .title(normalizedTitle) - .color(normalizedColor) - .message("리스트가 생성되었습니다.") - .build() - ); - } + public CommonResponse<UserPlaceCreateResponseDto> createPlaceList( + @Parameter(hidden = true) @CurrentUser Long userId, + @RequestBody @Valid UserPlaceCreateRequestDto request + ) { + return CommonResponse.success(userPlaceService.createPlaceList(userId, request)); + }권장 변경(서비스; 시그니처/반환 타입 변경 및 DTO 생성):
- public Long createPlaceList(Long userId, UserPlaceCreateRequestDto request) { + public UserPlaceCreateResponseDto createPlaceList(Long userId, UserPlaceCreateRequestDto request) { @@ - return saved.getId(); + return UserPlaceCreateResponseDto.builder() + .placeId(saved.getId()) + .title(saved.getTitle()) + .color(saved.getColor()) + .message("리스트가 생성되었습니다.") + .build();
73-86: 다중 리스트 추가 엔드포인트: 입력 한도/오류 요약 개선 제안
- 대량 placeIds 요청 시 처리 한도(@SiZe(max=...))를 DTO에 명시하면 API 보호에 유익합니다.
- 반환 메시지 외에 상세 결과(성공/스킵된 placeId 목록)를 포함한 응답 DTO를 제공하면 클라이언트 UX가 좋아집니다.
원하시면 응답 DTO 스펙과 컨트롤러/서비스 수정 패치를 제안하겠습니다.
96-104: placeId에도 @min(1) 추가 권장page/size에는 제약이 있으나 placeId는 음수/0 허용 상태입니다. 간단히 @min(1)로 방어하는 것을 권장합니다.
적용 예시:
- public CommonResponse<Page<WaybleZoneListResponseDto>> getZonesInPlace( - @Parameter(hidden = true) @CurrentUser Long userId, - @RequestParam Long placeId, - @RequestParam(defaultValue = "1") @Min(1) Integer page, - @RequestParam(defaultValue = "20") @Min(1) @Max(100) Integer size - ) { + public CommonResponse<Page<WaybleZoneListResponseDto>> getZonesInPlace( + @Parameter(hidden = true) @CurrentUser Long userId, + @RequestParam @Min(1) Long placeId, + @RequestParam(defaultValue = "1") @Min(1) Integer page, + @RequestParam(defaultValue = "20") @Min(1) @Max(100) Integer size + ) {
📜 Review details
Configuration used: CodeRabbit UI
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 (6)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java(2 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java(0 hunks)src/main/java/com/wayble/server/user/dto/UserResponseDto.java(0 hunks)src/main/java/com/wayble/server/user/exception/UserErrorCase.java(1 hunks)src/main/java/com/wayble/server/user/service/UserPlaceService.java(4 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java
- src/main/java/com/wayble/server/user/dto/UserResponseDto.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java (3)
src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (1)
UserPlaceRemoveRequestDto(5-8)src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java (1)
UserPlaceRequestDto(5-9)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)
src/main/java/com/wayble/server/user/exception/UserErrorCase.java (1)
src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
Entity(13-47)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (2)
src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java (1)
RequiredArgsConstructor(13-43)src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (1)
UserPlaceRemoveRequestDto(5-8)
src/main/java/com/wayble/server/user/service/UserPlaceService.java (1)
src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)
⏰ 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: run-docker-image-on-ec2
🔇 Additional comments (5)
src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java (1)
8-8: 컨테이너 요소 제약 사용 적절List<@NotNull Long>로 null 요소 방지한 점 좋습니다. Bean Validation 컨테이너 요소 제약이 활성화된 환경(예: Spring Boot 3.x + Hibernate Validator)에서 잘 동작합니다.
src/main/java/com/wayble/server/user/exception/UserErrorCase.java (1)
25-26: 409 Conflict로의 중복 타이틀 에러 추가 적절
- PLACE_TITLE_DUPLICATED(409, 1016, …) 추가와 이전 항목 쉼표 처리 수정 모두 타당합니다.
- 서비스에서 애플리케이션/DB 레벨 모두에서 이 에러로 일관 번역되는지 확인만 부탁드립니다.
src/main/java/com/wayble/server/user/service/UserPlaceService.java (3)
90-92: zone 저장을 1회로 모은 점 좋습니다루프 후 added>0 일괄 저장으로 write 횟수를 최소화한 점 좋습니다.
120-120: 0-based 페이지 변환 로직 적합 (컨트롤러의 @min(1)과 연동 OK)page - 1로 0 기반 변환이 맞습니다. 컨트롤러에서 page에 @min(1)을 적용했으므로 음수 위험도 없습니다.
39-58: DB 유니크 제약 확인 및 DataIntegrityViolation 예외 처리 추가 권장
- 애플리케이션 레벨 선행 조회만으로는 동시 요청 시 중복 타이틀 생성이 가능합니다.
→ DB 레벨(user_place(user_id, title) + 활성 조건) 유니크 제약을 추가하고,DataIntegrityViolationException을 잡아PLACE_TITLE_DUPLICATED로 매핑해 주세요.request.title()이 null일 경우 NPE가 발생할 수 있습니다.
→ DTO에@NotBlank또는 비슷한 검증 어노테이션이 적용되어 있는지 확인 바랍니다.- DB 마이그레이션(예:
src/main/resources/db/migration/)에서user_place(user_id, title)유니크 제약이 존재하는지 확인하고, 없으면 스크립트에 추가해 주세요.수정 예시 (
src/main/java/com/wayble/server/user/service/UserPlaceService.java):try { UserPlace saved = userPlaceRepository.save( UserPlace.builder() .title(normalizedTitle) .color(color) .user(user) .build() ); return saved.getId(); - } catch (Exception e) { - // ... + } catch (DataIntegrityViolationException e) { + // DB 유니크 제약 위반 → 애플리케이션 예외로 매핑 + throw new ApplicationException(UserErrorCase.PLACE_TITLE_DUPLICATED); }위 사항들이 모두 적용되었는지 확인 부탁드립니다.
| @Transactional | ||
| public int addZoneToPlaces(Long userId, List<Long> placeIds, Long waybleZoneId) { | ||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); | ||
|
|
||
| WaybleZone zone = waybleZoneRepository.findById(waybleZoneId) | ||
| .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); | ||
|
|
||
| // 중복 저장 확인 | ||
| boolean duplicated = mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, request.waybleZoneId()); | ||
| if (duplicated) { | ||
| throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED); | ||
| Set<Long> uniquePlaceIds = new LinkedHashSet<>(placeIds); | ||
|
|
||
| int added = 0; | ||
| for (Long placeId : uniquePlaceIds) { | ||
| UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, user.getId()) | ||
| .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); | ||
|
|
||
| boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId); | ||
| if (exists) continue; | ||
|
|
||
| mappingRepository.save(UserPlaceWaybleZoneMapping.builder() | ||
| .userPlace(place) | ||
| .waybleZone(zone) | ||
| .build()); | ||
|
|
||
| place.increaseCount(); | ||
| userPlaceRepository.save(place); | ||
|
|
||
| zone.addLikes(1); // 리스트 하나에 추가될 때마다 +1 | ||
| added++; | ||
| } | ||
|
|
||
| String color = (request.color() == null || request.color().isBlank()) ? "GRAY" : request.color(); | ||
| UserPlace userPlace = userPlaceRepository.findByUser_IdAndTitle(userId, request.title()) | ||
| .orElseGet(() -> userPlaceRepository.save( | ||
| UserPlace.builder() | ||
| .title(request.title()) | ||
| .color(color) | ||
| .user(user) | ||
| .build() | ||
| )); | ||
|
|
||
| mappingRepository.save(UserPlaceWaybleZoneMapping.builder() | ||
| .userPlace(userPlace) | ||
| .waybleZone(waybleZone) | ||
| .build()); | ||
|
|
||
| userPlace.increaseCount(); | ||
| userPlaceRepository.save(userPlace); | ||
|
|
||
| waybleZone.addLikes(1); | ||
| waybleZoneRepository.save(waybleZone); | ||
| if (added > 0) { | ||
| waybleZoneRepository.save(zone); | ||
| } | ||
| return added; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
다중 리스트 추가 시 경쟁 조건/성능 개선
- 경쟁 조건: existsBy... → save 사이 레이스로 중복 매핑이 생길 수 있습니다. 매핑 테이블에 유니크 인덱스(user_place_id, wayble_zone_id)를 두고 save 시 DataIntegrityViolationException을 무시/변환하는 방식으로 보강하세요.
- 성능: place 별 exists 쿼리/개별 save가 다수 발생합니다. 한 번에 “이미 매핑된 placeId 집합”을 조회하여 차집합만 insert하면 왕복 횟수를 줄일 수 있습니다.
적용 최소 변경(예시: 유니크 제약을 가정한 try/catch):
@@
- boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
- if (exists) continue;
-
- mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
- .userPlace(place)
- .waybleZone(zone)
- .build());
-
- place.increaseCount();
- userPlaceRepository.save(place);
-
- zone.addLikes(1); // 리스트 하나에 추가될 때마다 +1
- added++;
+ boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
+ if (exists) continue;
+ try {
+ mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
+ .userPlace(place)
+ .waybleZone(zone)
+ .build());
+ place.increaseCount();
+ userPlaceRepository.save(place);
+ zone.addLikes(1); // 리스트 하나에 추가될 때마다 +1
+ added++;
+ } catch (org.springframework.dao.DataIntegrityViolationException ex) {
+ // 동시성으로 인해 유니크 제약 위반 시 스킵
+ }추가 최적화(선택): Repository에 다음과 같은 메서드를 추가해 이미 매핑된 placeId들을 한 번에 조회 후 차집합만 insert 하세요.
- existsBy... 반복 대신: findAllPlaceIdsByWaybleZoneIdAndUserPlaceIdIn(waybleZoneId, placeIds)
- 차집합 = 요청 placeIds - 이미 매핑된 placeIds
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| public int addZoneToPlaces(Long userId, List<Long> placeIds, Long waybleZoneId) { | |
| User user = userRepository.findById(userId) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); | |
| WaybleZone zone = waybleZoneRepository.findById(waybleZoneId) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); | |
| // 중복 저장 확인 | |
| boolean duplicated = mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, request.waybleZoneId()); | |
| if (duplicated) { | |
| throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED); | |
| Set<Long> uniquePlaceIds = new LinkedHashSet<>(placeIds); | |
| int added = 0; | |
| for (Long placeId : uniquePlaceIds) { | |
| UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, user.getId()) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); | |
| boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId); | |
| if (exists) continue; | |
| mappingRepository.save(UserPlaceWaybleZoneMapping.builder() | |
| .userPlace(place) | |
| .waybleZone(zone) | |
| .build()); | |
| place.increaseCount(); | |
| userPlaceRepository.save(place); | |
| zone.addLikes(1); // 리스트 하나에 추가될 때마다 +1 | |
| added++; | |
| } | |
| String color = (request.color() == null || request.color().isBlank()) ? "GRAY" : request.color(); | |
| UserPlace userPlace = userPlaceRepository.findByUser_IdAndTitle(userId, request.title()) | |
| .orElseGet(() -> userPlaceRepository.save( | |
| UserPlace.builder() | |
| .title(request.title()) | |
| .color(color) | |
| .user(user) | |
| .build() | |
| )); | |
| mappingRepository.save(UserPlaceWaybleZoneMapping.builder() | |
| .userPlace(userPlace) | |
| .waybleZone(waybleZone) | |
| .build()); | |
| userPlace.increaseCount(); | |
| userPlaceRepository.save(userPlace); | |
| waybleZone.addLikes(1); | |
| waybleZoneRepository.save(waybleZone); | |
| if (added > 0) { | |
| waybleZoneRepository.save(zone); | |
| } | |
| return added; | |
| } | |
| @Transactional | |
| public int addZoneToPlaces(Long userId, List<Long> placeIds, Long waybleZoneId) { | |
| User user = userRepository.findById(userId) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); | |
| WaybleZone zone = waybleZoneRepository.findById(waybleZoneId) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); | |
| Set<Long> uniquePlaceIds = new LinkedHashSet<>(placeIds); | |
| int added = 0; | |
| for (Long placeId : uniquePlaceIds) { | |
| UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, user.getId()) | |
| .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); | |
| boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId); | |
| if (exists) continue; | |
| try { | |
| mappingRepository.save(UserPlaceWaybleZoneMapping.builder() | |
| .userPlace(place) | |
| .waybleZone(zone) | |
| .build()); | |
| place.increaseCount(); | |
| userPlaceRepository.save(place); | |
| zone.addLikes(1); // 리스트 하나에 추가될 때마다 +1 | |
| added++; | |
| } catch (org.springframework.dao.DataIntegrityViolationException ex) { | |
| // 동시성으로 인해 유니크 제약 위반 시 스킵 | |
| } | |
| } | |
| if (added > 0) { | |
| waybleZoneRepository.save(zone); | |
| } | |
| return added; | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/user/service/UserPlaceService.java around
lines 60-94, the current per-place exists()/save() loop causes a race condition
and extra DB round-trips; add a unique DB constraint on (user_place_id,
wayble_zone_id) and change the logic to (1) query existing mappings in bulk
using a new repository method like
findAllPlaceIdsByWaybleZoneIdAndUserPlaceIdIn(waybleZoneId, placeIds), (2)
compute the difference = requestedPlaceIds - alreadyMappedPlaceIds, (3) for only
the difference create UserPlaceWaybleZoneMapping entities and batch-save them,
increment the corresponding UserPlace counts and the WaybleZone likes in-memory,
then save updated entities once; additionally, keep a defensive try/catch around
the batch save to catch DataIntegrityViolationException and ignore/translate
duplicates (to handle concurrent inserts) so duplicates don’t fail the whole
transaction.
✔️ 연관 이슈
📝 작업 내용
스크린샷 (선택)
Summary by CodeRabbit
New Features
Refactor
Tests
Chores