-
Notifications
You must be signed in to change notification settings - Fork 1
[release] 진행상황 Main 브랜치에 반영 #157
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
…ring into feature/seungin
[REFACTOR] 유저 장소 저장 관련 api 개선 및 리뷰 등록 칼럼 변경
[FIX] 웨이블 마커 및 polyline 수정
Walkthrough
Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Controller as ReviewController
participant Security as SecurityContext
participant Service as ReviewService
participant Repo as Repositories
User->>Controller: POST /reviews (zoneId, dto)
Controller->>Security: extract Authentication
Security-->>Controller: userId
Controller->>Service: registerReview(zoneId, userId, dto)
Service->>Repo: find Zone, find User
Service->>Repo: save Review (+images if any)
Service->>Repo: update Zone rating/reviewCount, save
Service-->>Controller: void
Controller-->>User: 200 OK
sequenceDiagram
actor User
participant Ctl as UserPlaceController
participant Sec as SecurityContext
participant Svc as UserPlaceService
participant Repo as UserPlace/Mapping/Zone Repos
User->>Ctl: GET /user-places/summaries?sort=latest|title
Ctl->>Sec: extract userId
Sec-->>Ctl: userId
Ctl->>Svc: getMyPlaceSummaries(userId, sort)
Svc->>Repo: findAllByUserId (sorted)
Svc-->>Ctl: List<UserPlaceSummaryDto>
Ctl-->>User: 200 OK
User->>Ctl: GET /user-places/zones?placeId=&page=&size=
Ctl->>Sec: extract userId
Ctl->>Svc: getZonesInPlace(userId, placeId, page, size)
Svc->>Repo: findByIdAndUserId / findZonesByPlaceId(pageable)
Svc-->>Ctl: Page<WaybleZoneListResponseDto>
Ctl-->>User: 200 OK
User->>Ctl: DELETE /user-places/zone (placeId, waybleZoneId)
Ctl->>Sec: extract userId
Ctl->>Svc: removeZoneFromPlace(userId, placeId, waybleZoneId)
Svc->>Repo: exists mapping? then delete
Svc->>Repo: dec savedCount, dec likes, persist
Svc-->>Ctl: void
Ctl-->>User: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (3)
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (2)
125-129: 거리 계산 시 위도/경도 전달 순서가 뒤바뀌었습니다폴리라인이 [lat, lon]으로 바뀌었는데, HaversineUtil 호출부가 여전히 [lon, lat] 순서로 값을 전달하고 있습니다. 거리 오산 가능성이 큽니다. 아래와 같이 순서를 교정해 주세요.
- totalDistance += HaversineUtil.haversine( - polyline.get(i - 1)[1], polyline.get(i - 1)[0], - polyline.get(i)[1], polyline.get(i)[0] - ); + totalDistance += HaversineUtil.haversine( + polyline.get(i - 1)[0], polyline.get(i - 1)[1], + polyline.get(i)[0], polyline.get(i)[1] + );
24-25: 거리 단위 일관성 검증 완료: HaversineUtil은 미터 단위 반환
HaversineUtil 내부에서 EARTH_RADIUS = 6 371 000 (m)으로 설정되어 있어 반환 값이 미터입니다.
따라서 MAX_DISTANCE(30000.0)와totalDistanceMeters변수명은 그대로 유지해도 무방합니다.다만, 이전 코멘트에서 언급된 위도/경도 순서가 올바르게 전달되는지 아래 호출부를 확인하여
(lat, lon)순서로 수정해 주세요:
- WalkingService.java (57, 59행)
- GraphInit.java (95행)
- WaybleDijkstraService.java (126–128행)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (1)
40-41: SecurityContext principal 직접 캐스팅은 ClassCastException 위험 — extractUserId() 사용으로 통일다른 엔드포인트는 extractUserId()를 사용하지만 여기만 Long 캐스팅을 직접 수행하고 있습니다. principal 타입이 Long이 아닐 경우 런타임 예외가 발생할 수 있습니다. 아래처럼 통일해 주세요.
적용 diff:
- Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - userPlaceService.saveUserPlace(userId, request); // userId 파라미터로 넘김 + Long userId = extractUserId(); + userPlaceService.saveUserPlace(userId, request);
🧹 Nitpick comments (19)
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (2)
38-40: 노드 간 5초 대기는 (간선 수) 기준이 자연스럽습니다“노드 간 5초”라면 edge 개수(path.size()-1) 기준이 더 적절합니다. 오프바이원 보정을 제안합니다.
- int totalTime = (int) Math.round(calculateTime(path)) + path.size() * 5; + int totalTime = (int) Math.round(calculateTime(path)) + Math.max(0, path.size() - 1) * 5;
137-156: PriorityQueue<double[]> 사용 시 ID 정밀도 손실 가능성 — 타입 안전한 구조 권장노드 ID를 double[]에 담으면 큰 ID에서 부동소수점 정밀도 손실 위험이 있습니다. 타입 안정성과 가독성을 위해 전용 타입 사용을 권장합니다.
- PriorityQueue<double[]> pq = new PriorityQueue<>(Comparator.comparingDouble(value -> value[1])); + PriorityQueue<NodeDist> pq = new PriorityQueue<>(Comparator.comparingDouble(NodeDist::dist)); ... - pq.add(new double[]{start, 0.0}); + pq.add(new NodeDist(start, 0.0)); ... - double[] current = pq.poll(); - long u = (long) current[0]; + NodeDist current = pq.poll(); + long u = current.nodeId(); ... - pq.add(new double[]{edge.to(), alt}); + pq.add(new NodeDist(edge.to(), alt));다음 레코드를 클래스 내부(메서드 외부)에 추가해 주세요:
private record NodeDist(long nodeId, double dist) {}src/main/java/com/wayble/server/review/service/ReviewService.java (3)
47-51: 이미지 NPE 가드 추가는 👍 — saveAll로 일괄 저장해 DB 라운드트립을 줄이세요현재는 이미지마다 save를 호출합니다. saveAll로 변경하면 DB round-trip을 크게 줄일 수 있습니다.
아래처럼 교체를 제안합니다:
- if (dto.images() != null && !dto.images().isEmpty()) { - for (String imageUrl : dto.images()) { - reviewImageRepository.save(ReviewImage.of(review, imageUrl)); - } - } + if (dto.images() != null && !dto.images().isEmpty()) { + reviewImageRepository.saveAll( + dto.images().stream() + .map(url -> ReviewImage.of(review, url)) + .collect(Collectors.toList()) + ); + }
53-53: 불필요한 save 호출일 가능성 — JPA 관리 엔티티면 생략 가능트랜잭션 내에서 관리 중인 zone의 필드만 변경했다면 flush 시 자동 반영됩니다. 명시 save는 중복일 수 있어 삭제를 권장합니다.
아래처럼 정리 가능:
- waybleZoneRepository.save(zone);
33-54: 동시성 문제: 평균 및 리뷰 수 업데이트 로직 보완 권장현재
registerReview메서드는 동일한WaybleZone에 대한 동시 리뷰 등록 시, 평균(rating)과 리뷰 수(reviewCount)가 겹쳐 쓰여 왜곡될 위험이 있습니다. 아래 중 하나를 적용해 충돌 상황을 방지하세요.• 비관적 잠금 적용
조회 시 PESSIMISTIC_WRITE 락 적용
예시:
// 서비스 레이어 WaybleZone zone = waybleZoneRepository.findByIdForUpdate(zoneId) .orElseThrow(() -> new ApplicationException(WaybleZoneErrorCase.WAYBLE_ZONE_NOT_FOUND)); // 레포지토리 public interface WaybleZoneRepository extends JpaRepository<WaybleZone, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select z from WaybleZone z where z.id = :id") Optional<WaybleZone> findByIdForUpdate(@Param("id") Long id); }• 낙관적 잠금 적용
WaybleZone엔티티에@Version필드 추가- 업데이트 충돌 시
OptimisticLockException발생 → 재시도 로직 구현• 원자적 업데이트 사용
- 총합(sum) 컬럼을 추가해 단일 쿼리로
sum,count를 동시에 증가@Modifying+ JPQL/Native query로 레이스 컨디션 방지src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java (1)
5-11: savedCount 자료형을 int → long으로 상향을 권장합니다.DB 카운트/통계성 필드는 long(BigInt)로 흘러오는 경우가 흔합니다. 여유 용량과 일관성을 위해 long 사용을 권장드립니다.
public record UserPlaceSummaryDto( Long placeId, String title, String color, - int savedCount + long savedCount ) {}src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (1)
3-8: ID 유효성 검증을 보강하세요: @positive 추가 권장.ID는 0/음수가 올 수 없으므로 @positive 추가를 권장합니다. (메시지는 프로젝트 규칙에 맞춰 선택)
-import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; public record UserPlaceRemoveRequestDto( - @NotNull Long placeId, - @NotNull Long waybleZoneId + @NotNull @Positive Long placeId, + @NotNull @Positive Long waybleZoneId ) {}src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java (1)
7-9: color 입력값 포맷 검증 추가 권장(+ title은 NotBlank 고려).
- color가 UI 색상으로 소비된다면 허용 포맷(예: #RGB/#RRGGBB) 검증을 권장합니다.
- title은 공백 문자열 방지를 위해 @notblank가 더 적합할 수 있습니다. (기존 클라이언트 영향 검토 필요)
적용 예:
public record UserPlaceRequestDto( @NotNull Long waybleZoneId, - @NotNull String title, - String color + @NotBlank String title, + @Pattern(regexp = "^#(?:[0-9a-fA-F]{3}){1,2}$") String color ) {}imports 추가(파일 상단):
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern;기존 클라이언트에서 빈 문자열 title을 허용/전달하는지 점검해 주세요.
src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java (1)
23-36: 페이지 조회 시 N+1 가능성: 페치 전략/프로젝션 고려Page를 반환하고 서비스에서 DTO로 매핑한다면, WaybleZone의 연관 로딩(시설 등) 시 N+1이 발생할 수 있습니다. 아래 중 하나를 고려해 주세요.
- JPQL fetch join으로 필요한 연관만 미리 로딩
- @entitygraph(attributePaths=...) 적용
- DTO 프로젝션(생성자/인터페이스 기반)으로 필요한 컬럼만 조회
정렬은 m.id desc로 최신 매핑부터 보여주는 의도는 전달되나, 생성시각을 정확히 원하시면 매핑 엔티티에 createdAt을 두고 그 기준으로 정렬하는 방안을 권장합니다.
src/main/java/com/wayble/server/user/entity/UserPlace.java (1)
30-35: color 문자열과 savedCount 컬럼 기본값 관련 고려사항
- color: 자유 문자열로 두면 값 통제가 어려워집니다. UI 팔레트가 정해져 있다면 enum(예: GRAY, BLUE, …)으로 제한하거나, 최소한 길이/패턴 검증을 서비스/DTO 단계에 추가하는 것을 권장합니다.
- savedCount: 성능상 캐시 컬럼으로 유용하나, 파생값이라 데이터 일관성 이슈가 발생할 수 있습니다(동시성/수동 수정 등). 정합성 복구용 주기적 재계산 배치나 관리용 리페어 쿼리를 준비해 두면 안전합니다.
src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java (1)
11-13: 정렬별 메서드 분리 OK. Pageable 정렬로 통합도 고려latest/title 정렬 메서드 분리가 명확하긴 하나, findAllByUser_Id(userId, pageable) 하나로 통합하고 Sort 파라미터로 제어하면 확장성/테스트 편의가 좋아집니다.
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (3)
52-58: sort 파라미터 허용값 검증 권장현재 문자열 그대로 서비스로 전달됩니다. 허용값(latest/title 등)을 화이트리스트로 검증하거나 enum으로 바꾸면 오입력 방지 및 스펙 명확화에 도움이 됩니다. 잘못된 값은 기본값(latest)로 폴백하는 로직을 서비스에 두는 것도 방법입니다.
96-121: System.err 대신 로거 사용 + 예외 유형 통일 권장
- 숫자 변환 실패 로그를 System.err로 직접 출력하면 제어/마스킹이 어렵습니다. 로거로 치환해 주세요.
- 인증 정보 미존재/파싱 실패는 IllegalStateException 대신 인증/인가 예외(AuthenticationCredentialsNotFoundException/AccessDeniedException 등)로 통일하면 전역 예외 처리와 HTTP 상태코드 매핑이 수월합니다.
적용 diff(로그 대체):
- // 숫자 변환 실패 시 출력 - System.err.println("Principal 문자열을 Long으로 변환할 수 없습니다: " + s); + // 숫자 변환 실패 시 경고 로그 + log.warn("Principal 문자열을 Long으로 변환할 수 없습니다. principal='{}'", s);클래스 상단에 로거 추가(선택 적용):
import org.slf4j.Logger; import org.slf4j.LoggerFactory; // 클래스 내부 필드 private static final Logger log = LoggerFactory.getLogger(UserPlaceController.class);
69-77: 페이지 인덱스 0-base 변환 확인 및 추가 검증 제안
서비스(UserPlaceService.java:98)에서page - 1보정이 이미 적용되어 있어 첫 페이지가 누락되지 않습니다.
추가로, 컨트롤러 메서드의page및size파라미터에 아래와 같은 Bean Validation 어노테이션을 적용해 과도한 입력을 방지하는 것을 권장드립니다:src/main/java/com/wayble/server/user/service/UserPlaceService.java (5)
17-23: DataIntegrityViolationException import 누락 가능성위 경쟁 조건 대응(예외 변환) 적용 시 스프링 DAO 예외를 import 해야 합니다.
import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort;
52-61: 제목/색상 입력값 정제 및 중복 제목 유니크 제약 검토
- 제목(title) 공백/널 처리: 현재 그대로 사용하면 공백 제목이 그대로 저장될 수 있습니다. trim + 유효성 검사를 권장합니다.
- 색상(color) 기본값: 서비스단 기본값 “GRAY” 지정은 좋으나, 엔티티/DB 기본값으로도 일관되게 보강하면 입력 누락 시 정합성이 보장됩니다.
- 기존 장소 재사용 시 색상 업데이트: 동일 제목으로 기존 장소를 재사용하는 경우 요청에 색상이 전달되면
updateColor로 반영하는 것이 자연스러울 수 있습니다(의도한 동작인지 확인 필요).- (선택) UserPlace (user_id, title) 유니크 제약을 추가해 동시성 하에서 중복 장소 레코드 생성 방지 권장.
79-82: sort 파라미터 처리 간소/엄격화 제안현재 "name"/"title"일 때만 제목 오름차순, 그 외는 생성일 내림차순입니다. 프런트 계약이 더 확장될 여지가 있으면 enum(예: NAME, CREATED_AT_DESC)으로 명시화하거나, 허용값 외 입력 시 400을 주는 것도 고려해 볼 만합니다. 현 상태도 동작상 문제는 없습니다.
98-101: page/size 파라미터 경계값 대책 추가 권장
- page는 1-base를 0-base로 안전하게 변환하고 있어 좋습니다.
- size에 하한/상한이 없어 매우 큰 값 요청 시 부하가 발생할 수 있습니다. 최소 1, 최대 N(예: 100)으로 클램프를 권장합니다.
- Pageable pageable = PageRequest.of(zeroBased, size, Sort.by(Sort.Direction.DESC, "id")); + int boundedSize = Math.max(1, Math.min(size, 100)); // TODO: 필요 시 설정값으로 추출 + Pageable pageable = PageRequest.of(zeroBased, boundedSize, Sort.by(Sort.Direction.DESC, "id"));
103-115: DTO 매핑 NPE 가드 및 null 대신 빈 컬렉션 반환 제안
z.getAddress()가 null일 경우toFullAddress()에서 NPE가 날 수 있습니다. 널 가드를 권장합니다.facilities(null)대신 빈 컬렉션을 반환하면 클라이언트 단 null 체크를 줄일 수 있습니다.- .address(z.getAddress().toFullAddress()) + .address(z.getAddress() != null ? z.getAddress().toFullAddress() : null) @@ - .facilities(null) + .facilities(java.util.Collections.emptyList())위 변경이 DTO 계약과 충돌하지 않는지(필드가 Optional/nullable인지, 빈 리스트 허용인지) 확인이 필요합니다.
📜 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 (15)
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java(1 hunks)src/main/java/com/wayble/server/review/controller/ReviewController.java(3 hunks)src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java(0 hunks)src/main/java/com/wayble/server/review/service/ReviewService.java(2 hunks)src/main/java/com/wayble/server/user/controller/UserPlaceController.java(2 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java(1 hunks)src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java(1 hunks)src/main/java/com/wayble/server/user/entity/UserPlace.java(2 hunks)src/main/java/com/wayble/server/user/exception/UserErrorCase.java(1 hunks)src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java(1 hunks)src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java(2 hunks)src/main/java/com/wayble/server/user/service/UserPlaceService.java(2 hunks)src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java (3)
src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (2)
Builder(7-22)Builder(13-21)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)src/main/java/com/wayble/server/user/dto/UserRegisterRequestDto.java (1)
UserRegisterRequestDto(7-17)
src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java (5)
src/main/java/com/wayble/server/common/response/CommonResponse.java (1)
Builder(8-42)src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (2)
Builder(7-22)Builder(13-21)src/main/java/com/wayble/server/wayblezone/dto/WaybleZoneListResponseDto.java (1)
Builder(6-17)src/main/java/com/wayble/server/explore/dto/common/WaybleZoneInfoResponseDto.java (1)
Builder(7-34)src/main/java/com/wayble/server/explore/dto/search/response/WaybleZoneDistrictResponseDto.java (1)
Builder(7-21)
src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java (2)
src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (2)
Builder(7-22)Builder(13-21)src/main/java/com/wayble/server/direction/dto/request/PlaceSaveRequest.java (2)
Builder(13-17)Builder(8-18)
src/main/java/com/wayble/server/user/exception/UserErrorCase.java (6)
src/main/java/com/wayble/server/wayblezone/exception/WaybleZoneErrorCase.java (1)
Getter(7-18)src/main/java/com/wayble/server/direction/exception/WalkingErrorCase.java (1)
Getter(7-21)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)src/main/java/com/wayble/server/wayblezone/service/WaybleZoneService.java (1)
WaybleZoneErrorCase(66-66)src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java (1)
Getter(7-17)src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (1)
Builder(7-22)
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (1)
src/test/java/com/wayble/server/direction/service/WalkingServiceTest.java (1)
BeforeEach(35-77)
src/main/java/com/wayble/server/review/service/ReviewService.java (2)
src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java (1)
Schema(8-36)src/main/java/com/wayble/server/review/entity/Review.java (1)
Entity(14-65)
src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java (1)
src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)
src/main/java/com/wayble/server/user/service/UserPlaceService.java (2)
src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (1)
Builder(7-22)
src/main/java/com/wayble/server/user/controller/UserPlaceController.java (4)
src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java (1)
RequiredArgsConstructor(13-43)src/main/java/com/wayble/server/user/dto/UserPlaceListResponseDto.java (1)
Builder(7-22)src/main/java/com/wayble/server/wayblezone/controller/WaybleZoneController.java (1)
RestController(21-65)src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java (1)
Entity(10-29)
src/main/java/com/wayble/server/user/entity/UserPlace.java (3)
src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java (2)
Modifying(82-88)Modifying(57-63)src/main/java/com/wayble/server/user/entity/User.java (1)
Getter(14-134)src/main/java/com/wayble/server/common/entity/BaseEntity.java (2)
MappedSuperclass(13-36)softDelete(33-35)
🔇 Additional comments (13)
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (2)
67-69: 지오메트리 좌표 [lon, lat] → [lat, lon] 변환 적용 적절합니다지오메트리 소스가 [lon, lat] 기준일 때, 폴리라인을 [lat, lon]으로 통일하는 현재 변환은 일관성 확보에 적합합니다. 중복 제거 로직과도 잘 맞습니다.
74-75: 노드 좌표도 [lat, lon] 순서로 통일 — OKfrom/to 좌표를 [lat, lon]으로 구성해 상단 WayblePoint 생성 및 폴리라인 처리와 일관됩니다.
src/main/java/com/wayble/server/review/service/ReviewService.java (1)
33-38: 시그니처 변경 및 DTO 정리 확인 완료
- ReviewRegisterDto에서
userId필드 및 검증이 제거된 것이 확인되었습니다.dto.userId()호출 잔재도 없습니다.클라이언트 요청 스펙/문서에서도 더 이상
userId를 요구하지 않도록 업데이트되었는지 최종 확인 부탁드립니다.src/main/java/com/wayble/server/review/controller/ReviewController.java (2)
14-15: AuthenticationPrincipal 주입 전 인증 주체 타입 확인 필요현재 코드베이스에서 커스텀
Principal(혹은UserDetails) 구현이나 JWT 기반 인증 설정이 발견되지 않아,extractUserId()를 제거하고
@AuthenticationPrincipal(expression = "id") Long userId로 바로 주입할 경우 컴파일 오류나 NPE가 발생할 여지가 있습니다.
리팩터링에 앞서 아래 사항을 검토해 주세요:
- 커스텀
UserPrincipal,CustomUserDetails등id프로퍼티를 갖는 인증 주체 클래스 존재 여부- JWT 방식 사용 시
JwtDecoder,SecurityFilterChain혹은JwtAuthenticationToken설정 활성화 여부- 위 구현이 없다면 별도 정의 없이
@AuthenticationPrincipal(expression = "id")로는 주입 불가확인이 완료되면 아래와 같이 수정하세요:
--- src/main/java/com/wayble/server/review/controller/ReviewController.java @@ -14,15 +14,15 -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -39,10 +39,8 @@ public class ReviewController { - public CommonResponse<String> registerReview( - @PathVariable Long waybleZoneId, - @RequestBody @Valid ReviewRegisterDto dto - ) { - Long userId = extractUserId(); // 토큰에서 유저 ID 추출 + public CommonResponse<String> registerReview( + @PathVariable Long waybleZoneId, + @RequestBody @Valid ReviewRegisterDto dto, + @AuthenticationPrincipal(expression = "id") Long userId ) { reviewService.registerReview(waybleZoneId, userId, dto); return CommonResponse.success("리뷰가 등록되었습니다."); @@ -61,19 +59,3 @@ public class ReviewController { - private Long extractUserId() { … } // 제거 + // extractUserId() 메서드 삭제변경 전후에 인증 실패 시 401/500 응답 매핑이 정상 동작하는지 반드시 테스트하세요.
41-45: DTO 및 API 문서 검증 완료
- ReviewRegisterDto(record)에서
userId필드 및 검증 항목이 완전히 제거되어 있습니다.- 각 필드에 대한 @Schema(example=…) 어노테이션에도
userId관련 예시가 존재하지 않습니다.- ReviewController의 @operation(summary…) 및 SpringDoc 설정에서 requestBody로
userId를 노출하지 않음을 확인했습니다.따라서 별도의 수정 없이 본 PR대로 진행해도 됩니다.
src/main/java/com/wayble/server/user/exception/UserErrorCase.java (1)
23-25: 에러 케이스 확장은 적절합니다.
- PLACE_NOT_FOUND, PLACE_MAPPING_NOT_FOUND 추가로 도메인 명확성이 좋아졌습니다.
- 쉼표/세미콜론 위치도 enum 문법에 맞게 정리되었습니다. LGTM.
src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java (1)
8-15: DTO 정의 깔끔합니다. 사용처만 확인 부탁레코드+빌더 조합으로 불변 응답 DTO 구성 적절합니다. 다만 현재 컨트롤러에선 Page를 직접 반환하고 있어 본 DTO의 실제 사용처가 보이지 않습니다. 사내 다른 엔드포인트에서 사용 중인지 확인해 주세요. 미사용이라면 제거 고려가치 있습니다.
src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java (2)
19-21: 메서드 시그니처 일관성 좋습니다placeId+zoneId 기준 존재/삭제 메서드 추가로 서비스 단 로직 단순화에 도움이 됩니다. Derived delete 쿼리는 @Modifying 불필요하니 현재 형태가 맞습니다.
24-35: Java 15+ Text Block 지원 확인 완료
Gradlebuild.gradle에서 다음과 같이 Java 17 toolchain이 설정되어 있어 Java 15 이상 기능인 Text Block을 문제없이 사용할 수 있습니다. 별도 수정 사항 없습니다.• build.gradle, 11–13행:
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }src/main/java/com/wayble/server/user/entity/UserPlace.java (2)
18-20: 소프트 삭제 구성 적절합니다@SQLDelete + @SQLRestriction으로 일관된 소프트 삭제가 보장됩니다. BaseEntity.deletedAt와도 일치합니다.
42-47: 카운트 증감 가드 적절함 + 동시성은 서비스 트랜잭션으로 보호 필요0 미만 방지는 좋습니다. 다만 동시 저장/삭제가 많을 경우 일관성 유지를 위해 서비스 계층에서 동일 트랜잭션 내 카운트 증감과 매핑 insert/delete를 함께 처리(낙관적 락 또는 DB 제약 포함)하는지 확인 부탁드립니다.
src/main/java/com/wayble/server/user/service/UserPlaceService.java (2)
74-76: TX 경계 설정 적절함(LGTM)조회성 메서드에
@Transactional(readOnly = true)를 적용한 점 좋습니다. 불필요한 flush 방지와 성능 측면에서 유의미합니다.Also applies to: 93-94
74-91: 요약 응답 매핑 깔끔함(LGTM) + 스트림 toList 사용 적절
UserPlaceSummaryDto로의 매핑과 스트림 파이프라인이 간결합니다. 색상/카운트 노출도 요구사항과 부합해 보입니다.
| boolean duplicated = mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, request.waybleZoneId()); | ||
| if (duplicated) { | ||
| throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED); | ||
| } | ||
|
|
||
| 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); | ||
| } |
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
중복 저장 체크 범위 재확인 + 경쟁 조건 방지용 DB 유니크 제약/예외처리 필요
- 비즈니스 규칙 확인: 현재 중복 체크가 “유저의 모든 장소(UserPlace)”를 대상으로 합니다(
existsByUserPlace_User_IdAndWaybleZone_Id). 즉, 동일 유저가 같은 웨이블존을 서로 다른 장소에 중복 저장하는 것을 금지합니다. 의도된 정책인지 확인이 필요합니다. 일반적인 “목록(장소)별 저장” 모델이라면, 특정 장소 단위로만 중복을 금지하는 것이 자연스럽고, 그 경우에는existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)처럼 장소 기준으로 체크해야 합니다. - 동시성/정합성: 사전
exists체크 후save는 경쟁 조건(race)으로 중복 삽입이 발생할 수 있습니다(중복으로 저장되면 savedCount/likes가 과증가). 이를 방지하려면- 매핑 테이블(user_place_id, wayble_zone_id)에 DB 유니크 제약을 추가하고,
- 저장 시
DataIntegrityViolationException을 캐치해PLACE_ALREADY_SAVED로 변환하는 방식을 권장합니다.
- 구현 제안(서비스 레벨 예외 변환): 아래처럼 매핑 저장과 카운트 증가를 하나의 try-catch로 묶어주세요.
아래 diff는 본 메서드 내 경쟁 조건 대응(예외 변환)을 위한 최소 변경 예시입니다.
@@
- mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
- .userPlace(userPlace)
- .waybleZone(waybleZone)
- .build());
-
- userPlace.increaseCount();
- userPlaceRepository.save(userPlace);
-
- waybleZone.addLikes(1);
- waybleZoneRepository.save(waybleZone);
+ try {
+ mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
+ .userPlace(userPlace)
+ .waybleZone(waybleZone)
+ .build());
+
+ userPlace.increaseCount();
+ userPlaceRepository.save(userPlace);
+
+ waybleZone.addLikes(1);
+ waybleZoneRepository.save(waybleZone);
+ } catch (DataIntegrityViolationException e) {
+ // DB 유니크 제약 위반(중복 저장)에 대한 정합성 보장
+ throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED);
+ }또한, 매핑 엔티티에 유니크 제약을 반드시 추가해 주세요(별도 파일 변경):
// src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java
// @Table 애너테이션 보강
@Table(
name = "user_place_wayble_zone_mapping",
uniqueConstraints = @UniqueConstraint(
name = "uk_user_place_wayble_zone",
columnNames = {"user_place_id", "wayble_zone_id"}
)
)필요 시 “제목(장소명) 중복”도 방지하려면 UserPlace에도 (user_id, title) 유니크 제약을 권장합니다.
리포지토리 시그니처/유무 확인을 위한 간단 스캔 스크립트:
🏁 Script executed:
#!/bin/bash
# 리포지토리 메서드 존재/시그니처, 유니크 제약 여부 확인
rg -n -C2 'interface\s+UserPlaceWaybleZoneMappingRepository'
rg -n 'existsByUserPlace_User_IdAndWaybleZone_Id|existsByUserPlace_IdAndWaybleZone_Id|deleteByUserPlace_IdAndWaybleZone_Id|findZonesByPlaceId'
# 매핑 엔티티에 유니크 제약 존재 여부 확인
rg -n -C2 'class\s+UserPlaceWaybleZoneMapping'
rg -n 'uniqueConstraints|UniqueConstraint'
# UserPlace의 (user_id,title) 유니크 제약 존재 여부 확인
rg -n -C2 'class\s+UserPlace\b'
rg -n 'uniqueConstraints|UniqueConstraint'Length of output: 4417
비즈니스 룰 검증 및 DB 유니크 제약·예외 처리 추가 필요
- 비즈니스 규칙 확인:
현재mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, waybleZoneId)로 유저 전체 범위에서 중복 저장을 금지하고 있습니다.
특정 장소(user_place_id) 단위로만 중복을 막아야 한다면existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)로 변경이 필요하니 정책 여부 확인 부탁드립니다. - DB 유니크 제약 추가 (필수):
UserPlaceWaybleZoneMapping엔티티에(user_place_id, wayble_zone_id)에 대한 유니크 제약을 설정해야 경쟁 조건 시 중복 저장을 방지할 수 있습니다. - 서비스 레벨 예외 변환 (필수):
매핑 저장 및 카운트/좋아요 증가 로직을DataIntegrityViolationException으로 감싸, 위 DB 제약 위반 시PLACE_ALREADY_SAVED예외로 변환 처리해 주세요.
@@ src/main/java/com/wayble/server/user/service/UserPlaceService.java:47
- mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
- .userPlace(userPlace)
- .waybleZone(waybleZone)
- .build());
-
- userPlace.increaseCount();
- userPlaceRepository.save(userPlace);
-
- waybleZone.addLikes(1);
- waybleZoneRepository.save(waybleZone);
+ try {
+ mappingRepository.save(UserPlaceWaybleZoneMapping.builder()
+ .userPlace(userPlace)
+ .waybleZone(waybleZone)
+ .build());
+
+ userPlace.increaseCount();
+ userPlaceRepository.save(userPlace);
+
+ waybleZone.addLikes(1);
+ waybleZoneRepository.save(waybleZone);
+ } catch (DataIntegrityViolationException e) {
+ throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED);
+ }// src/main/java/com/wayble/server/user/entity/UserPlaceWaybleZoneMapping.java
@Table(
name = "user_place_wayble_zone_mapping",
uniqueConstraints = @UniqueConstraint(
name = "uk_user_place_wayble_zone",
columnNames = {"user_place_id", "wayble_zone_id"}
)
)선택 사항: 사용자 장소 자체에도 (user_id, title) 유니크 제약을 추가하면 동일 제목 중복 생성도 방지할 수 있습니다.
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/user/service/UserPlaceService.java around
lines 47-72, the current duplicate check uses
existsByUserPlace_User_IdAndWaybleZone_Id(userId, waybleZoneId) which enforces
uniqueness across a user rather than per user_place; confirm whether the check
should instead be existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)
and update accordingly. Add a DB unique constraint on UserPlaceWaybleZoneMapping
for (user_place_id, wayble_zone_id) to prevent race-condition duplicates (as
suggested in the review). Surround the mappingRepository.save plus the
userPlace.increaseCount() and waybleZone.addLikes() persistence steps with a
try-catch that catches DataIntegrityViolationException and converts it to throw
new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED) so DB-constraint
violations map to the correct business error.
| @Transactional | ||
| public void removeZoneFromPlace(Long userId, Long placeId, Long waybleZoneId) { | ||
| UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) | ||
| .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); | ||
|
|
||
| if (!mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)) { | ||
| throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND); | ||
| } | ||
|
|
||
| mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId); | ||
|
|
||
| place.decreaseCount(); | ||
| userPlaceRepository.save(place); | ||
|
|
||
| List<UserPlaceWaybleZoneMapping> mappings = mappingRepository.findAllByUserPlace_User_Id(userId); | ||
|
|
||
| return mappings.stream().map(mapping -> { | ||
| UserPlace userPlace = mapping.getUserPlace(); | ||
| WaybleZone waybleZone = mapping.getWaybleZone(); | ||
|
|
||
| // 웨이블존 대표 이미지 가져오기 | ||
| String imageUrl = waybleZone.getMainImageUrl(); | ||
|
|
||
| return UserPlaceListResponseDto.builder() | ||
| .placeId(userPlace.getId()) | ||
| .title(userPlace.getTitle()) | ||
| .waybleZone( | ||
| UserPlaceListResponseDto.WaybleZoneDto.builder() | ||
| .waybleZoneId(waybleZone.getId()) | ||
| .name(waybleZone.getZoneName()) | ||
| .category(waybleZone.getZoneType().toString()) | ||
| .rating(waybleZone.getRating()) | ||
| .address(waybleZone.getAddress().toFullAddress()) | ||
| .imageUrl(imageUrl) | ||
| .build() | ||
| ) | ||
| .build(); | ||
| }).toList(); | ||
| waybleZoneRepository.findById(waybleZoneId).ifPresent(z -> { | ||
| z.addLikes(-1); | ||
| waybleZoneRepository.save(z); | ||
| }); | ||
| } |
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
존 제거 시 exists→delete 2회 왕복으로 인한 경쟁 조건 위험; 단일 delete로 영향 행 수 확인 필요
현재 흐름:
- 존재 여부 확인
- 삭제
- savedCount/likes 감소
문제:
- 1)과 2) 사이에 다른 트랜잭션이 삭제하면, 2)에서 아무 것도 삭제되지 않아도 3) 카운터가 감소해 정합성이 깨질 수 있습니다.
개선:
deleteByUserPlace_IdAndWaybleZone_Id가 영향 행 수를 반환하도록(예:long) 정의하고, 0이면PLACE_MAPPING_NOT_FOUND를 던진 뒤에만 카운터를 감소시키세요.
- if (!mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)) {
- throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND);
- }
-
- mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
-
- place.decreaseCount();
- userPlaceRepository.save(place);
-
- waybleZoneRepository.findById(waybleZoneId).ifPresent(z -> {
- z.addLikes(-1);
- waybleZoneRepository.save(z);
- });
+ long deleted = mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
+ if (deleted == 0) {
+ throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND);
+ }
+
+ place.decreaseCount();
+ userPlaceRepository.save(place);
+
+ waybleZoneRepository.findById(waybleZoneId).ifPresent(z -> {
+ z.addLikes(-1);
+ waybleZoneRepository.save(z);
+ });리포지토리 시그니처 확인 스크립트:
🏁 Script executed:
#!/bin/bash
rg -n -C2 'interface\s+UserPlaceWaybleZoneMappingRepository'
rg -n 'deleteByUserPlace_IdAndWaybleZone_Id\('Length of output: 1119
UserPlaceWaybleZoneMappingRepository 및 removeZoneFromPlace 메서드 수정: delete 영향 행 수로 매핑 존재 여부 검증
현재 deleteByUserPlace_IdAndWaybleZone_Id가 void라 실제 삭제 여부를 알 수 없습니다. exists→delete 사이에 다른 트랜잭션이 매핑을 삭제하면 deleted=0임에도 카운터가 감소해 정합성이 깨질 수 있습니다.
수정 포인트
- UserPlaceWaybleZoneMappingRepository.deleteByUserPlace_IdAndWaybleZone_Id 시그니처를
long반환으로 변경 - removeZoneFromPlace에서 delete 반환값으로 매핑 존재 여부 판단 후 카운터 감소
--- a/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java
+++ b/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java
@@ public interface UserPlaceWaybleZoneMappingRepository extends JpaRepository<..., Long> {
- void deleteByUserPlace_IdAndWaybleZone_Id(Long placeId, Long zoneId);
+ @Modifying
+ @Query("DELETE FROM UserPlaceWaybleZoneMapping m WHERE m.userPlace.id = :placeId AND m.waybleZone.id = :zoneId")
+ long deleteByUserPlace_IdAndWaybleZone_Id(@Param("placeId") Long placeId,
+ @Param("zoneId") Long zoneId);
}--- a/src/main/java/com/wayble/server/user/service/UserPlaceService.java
+++ b/src/main/java/com/wayble/server/user/service/UserPlaceService.java
@@ public void removeZoneFromPlace(Long userId, Long placeId, Long waybleZoneId) {
- if (!mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)) {
- throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND);
- }
- mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
+ long deleted = mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId);
+ if (deleted == 0) {
+ throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND);
+ }
place.decreaseCount();
userPlaceRepository.save(place);
waybleZoneRepository.findById(waybleZoneId).ifPresent(z -> {
z.addLikes(-1);
waybleZoneRepository.save(z);
});🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/user/service/UserPlaceService.java around
lines 118 to 136, the method currently calls a void
deleteByUserPlace_IdAndWaybleZone_Id so we cannot know whether a row was
actually deleted; change
UserPlaceWaybleZoneMappingRepository.deleteByUserPlace_IdAndWaybleZone_Id to
return long (number of rows deleted) and in removeZoneFromPlace use that
returned count to determine if a mapping was removed — if deleteCount == 0 throw
ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND) and only then
decrement place and zone counters and save; keep the method @Transactional so
the check-and-delete outcome is consistent.
| public void addLikes(long count) { | ||
| this.likes += count; | ||
| if (this.likes < 0) this.likes = 0; | ||
| this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신 | ||
| } |
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
음수 방지 가드 추가는 합리적입니다. 동시성/오버플로 안전성과 DB 제약까지 함께 고려해 주세요.
- 현재 가드로 음수는 방지됩니다. 가독성 측면에선 Math.max 사용이 조금 더 명확합니다.
- 극단적인 경우 long 오버플로(이론상) 시 음수로 전환된 뒤 0으로 클램프되어 실제 의도(매우 큰 증가)가 왜곡될 수 있습니다. Math.addExact로 오버플로를 감지한 뒤 포화(saturating) 처리(Long.MAX_VALUE/0)까지 고려하면 더 안전합니다. 현실적으로는 +1/-1 증감만 사용한다면 과한 방어일 수 있어 선택 사항입니다.
- 경쟁 업데이트(동시에 같은 행의 likes 갱신)가 발생하면 JPA 기본 설정만으로는 잃어버린 업데이트가 생길 수 있습니다. @Version(낙관적 락) 또는 DB 원자적 업데이트 쿼리(예: update ... set likes = greatest(likes + :delta, 0) where id=:id)를 검토해 주세요. 후자는 동시성/클램프를 DB 레벨에서 일관되게 보장합니다.
가독성 개선(선호안):
public void addLikes(long count) {
- this.likes += count;
- if (this.likes < 0) this.likes = 0;
+ this.likes += count;
+ this.likes = Math.max(0, this.likes);
this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신
}오버플로 감지 + 포화 처리(선택안):
public void addLikes(long count) {
- this.likes += count;
- this.likes = Math.max(0, this.likes);
+ try {
+ this.likes = Math.addExact(this.likes, count);
+ } catch (ArithmeticException ex) {
+ this.likes = count > 0 ? Long.MAX_VALUE : 0L;
+ }
+ this.likes = Math.max(0, this.likes);
this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신
}DB 레벨 제약(선택, 클래스 레벨):
// imports
import org.hibernate.annotations.Check;
// 클래스 선언부에 추가
@Check(constraints = "likes >= 0")
public class WaybleZone extends BaseEntity {
...
}동시성 제어(@Version)와 사용 패턴(증감 범위)을 확인할 수 있도록 간단한 검색 스크립트를 제안합니다.
[suggest_optional_refactor]
[offer_architecture_advice]
🏁 Script executed:
#!/bin/bash
# WaybleZone에 @Version 사용 여부 확인
rg -n -C2 '@Version' --type=java | rg -n -C2 'WaybleZone|UserPlace|WaybleZone'
# likes 변경 호출부 사용 패턴 확인 (증감 범위를 파악)
rg -n -C3 '\baddLikes\s*\(' --type=java
# DB 레벨에서 likes를 원자적으로 갱신하는 커스텀 업데이트 쿼리 존재 여부 확인
rg -n -C2 -P 'update\s+\w+\s+set\s+.*likes' --type=javaLength of output: 2228
addLikes 메서드 가독성·오버플로 안전성 및 동시성 보강 제안
현재 검증 결과:
- Entity에 @Version 어노테이션이 적용되어 있지 않습니다.
- addLikes 호출부는 +1/–1 증감만 사용하므로 극단적 오버플로 가능성은 매우 낮습니다.
- DB 레벨에서 likes를 원자적으로 갱신하는 커스텀 쿼리도 존재하지 않습니다.
제안 사항:
- 가독성 향상: Math.max 사용
public void addLikes(long count) { - this.likes += count; - if (this.likes < 0) this.likes = 0; + this.likes += count; + this.likes = Math.max(0, this.likes); this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신 } - 오버플로 감지(선택): Math.addExact + 포화 처리
- 동시성 제어(권장):
• Entity에@Version추가 → 낙관적 락
• 또는 Repository에 원자적 업데이트 메서드 도입@Modifying @Query("update WaybleZone z set z.likes = greatest(z.likes + :delta, 0) where z.id = :id") int updateLikesAtomically(@Param("id") Long id, @Param("delta") long delta);
- DB 제약(선택):
클래스 레벨에@Check(constraints = "likes >= 0")추가
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java around
lines 101 to 105, the addLikes method should be made clearer and safer: replace
manual negative-check with Math.max to clamp at zero (or use Math.addExact with
try/catch and saturation to detect/handle overflow), and add optimistic locking
by annotating the entity with @Version to prevent lost updates under concurrency
(alternatively implement a repository-level atomic update method using a
@Modifying @Query that updates likes with greatest(likes + :delta, 0));
optionally add a class-level @Check(constraints = "likes >= 0") for a DB-side
constraint.
✔️ 연관 이슈
📝 작업 내용
Summary by CodeRabbit