[성능개선/버그] 스터디룸 지도 redis geo+tire 도입, 성능개선+버그수정#79
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough스터디룸 지도 및 검색 기능이 대대적으로 리팩토링되었습니다. Redis 기반의 Geo 및 Trie를 활용한 고성능 검색 서비스와 오토컴플리트가 도입되었으며, 거리 계산 로직은 별도의 유틸리티로 분리되어 최적화되었습니다. 기존 컨트롤러와 서비스는 구조가 개선되고, 입력 검증, 예외 처리, 캐싱, DB-Redis 동기화, 인덱스 최적화 등이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant StudyRoomMapController
participant StudyHallSearchService
participant RedisGeoService
participant RedisAutoCompleteService
participant DB
User->>StudyRoomMapController: /search/nearby-redis 요청
StudyRoomMapController->>StudyHallSearchService: searchNearbyStudyHallsWithRedis(request)
StudyHallSearchService->>RedisGeoService: findNearbyStudyHalls(lat, lng, radius, limit)
alt Redis 정상 동작
RedisGeoService-->>StudyHallSearchService: 위치 기반 검색 결과 반환
else Redis 장애
StudyHallSearchService->>DB: DB Bounding Box 쿼리
DB-->>StudyHallSearchService: DB 검색 결과 반환
end
StudyHallSearchService-->>StudyRoomMapController: 결과 반환
StudyRoomMapController-->>User: 응답 반환
sequenceDiagram
actor User
participant StudyRoomMapController
participant StudyHallSearchService
participant RedisAutoCompleteService
participant DB
User->>StudyRoomMapController: /search/autocomplete-redis 요청
StudyRoomMapController->>StudyHallSearchService: getSmartAutocompleteSuggestions(query, limit)
StudyHallSearchService->>RedisAutoCompleteService: getAutocompleteSuggestions(query, limit)
alt Redis 정상 동작
RedisAutoCompleteService-->>StudyHallSearchService: 오토컴플리트 결과 반환
else Redis 장애
StudyHallSearchService->>DB: DB 이름 LIKE 검색
DB-->>StudyHallSearchService: DB 검색 결과 반환
end
StudyHallSearchService-->>StudyRoomMapController: 결과 반환
StudyRoomMapController-->>User: 응답 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 PR의 모든 주요 변경사항은 이슈 #67의 리팩토링, 성능 개선, 구조 개선, 코드 개선 등 요구사항 범위 내에 있습니다. 기능과 무관한 불필요한 코드 변경은 발견되지 않았습니다. Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🧰 Additional context used🧠 Learnings (2)📚 Learning: 2025-08-07T04:57:32.640ZApplied to files:
📚 Learning: 2025-07-23T09:00:11.298ZApplied to files:
🧬 Code Graph Analysis (1)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Pull Request Overview
이 PR은 Redis를 활용한 스터디룸 지도 서비스의 성능 개선과 버그 수정을 목적으로 합니다. Redis Geo와 Trie 구조를 도입하여 지도 검색 성능을 5배, 자동완성 성능을 10-20배 향상시켰습니다.
주요 변경사항:
- Redis Geo 기반 위치 검색 시스템 도입으로 성능 개선
- Redis Trie 구조를 활용한 스마트 자동완성 기능 구현
- 검색 서비스 분리 및 하이브리드 검색 아키텍처 구현
Reviewed Changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| RedisConfig.java | Redis 템플릿 설정 개선 및 다중 용도별 Bean 추가 |
| RedisGeoService.java | Redis Geo를 활용한 위치 기반 검색 서비스 신규 구현 |
| RedisAutoCompleteService.java | Redis Trie 구조 기반 자동완성 서비스 신규 구현 |
| StudyHallSearchService.java | 검색 로직 분리 및 하이브리드 검색 시스템 구현 |
| StudyRoomMapController.java | Redis 기반 새로운 검색 API 추가 및 기존 API 개선 |
| DistanceCalculator.java | 거리 계산 로직 공통화를 위한 유틸리티 클래스 |
| StudyRoomConstants.java | 도메인 상수 정의 클래스 추가 |
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java
Outdated
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 24
🔭 Outside diff range comments (2)
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java (1)
89-97: 한국 좌표 검증 로직에서 상수 사용을 권장합니다.현재 하드코딩된 좌표 범위 값들을 StudyRoomConstants의 상수로 교체하여 일관성을 유지하세요.
public boolean isValidKoreanLocation() { if (latitude == null || longitude == null) { return false; } - // 한국 영역 대략적 범위 - return latitude >= 32.0 && latitude <= 39.0 && - longitude >= 123.0 && longitude <= 132.0; + // 한국 영역 대략적 범위 + return latitude >= StudyRoomConstants.KOREA_MIN_LATITUDE && + latitude <= StudyRoomConstants.KOREA_MAX_LATITUDE && + longitude >= StudyRoomConstants.KOREA_MIN_LONGITUDE && + longitude <= StudyRoomConstants.KOREA_MAX_LONGITUDE; }src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java (1)
64-73: StudyHall 필드 접근에서 일관성 문제가 있습니다.null 체크 후에도 여전히
studyRoom.getStudyHall()를 직접 호출하고 있습니다. 이미 추출한studyHall변수를 사용하여 일관성을 유지하세요.return StudyRoomDetailResponse.builder() .id(studyRoom.getId()) .name(studyRoom.getName()) .maxNum(studyRoom.getMaxNum()) .thumbnail(studyRoom.getThumbnail()) - .studyHallId(studyRoom.getStudyHall().getId()) - .studyHallName(studyRoom.getStudyHall().getName()) - .description(studyRoom.getStudyHall().getDescription()) - .simpleAddress(studyRoom.getStudyHall().getSimpleAddress()) - .address(studyRoom.getStudyHall().getAddress()) - .latitude(studyRoom.getStudyHall().getLatitude()) - .longitude(studyRoom.getStudyHall().getLongitude()) - .studyHallThumbnail(studyRoom.getStudyHall().getThumbnail()) - .openTime(studyRoom.getStudyHall().getOpenTime()) - .closeTime(studyRoom.getStudyHall().getCloseTime()) + .studyHallId(studyHall.getId()) + .studyHallName(studyHall.getName()) + .description(studyHall.getDescription()) + .simpleAddress(studyHall.getSimpleAddress()) + .address(studyHall.getAddress()) + .latitude(studyHall.getLatitude()) + .longitude(studyHall.getLongitude()) + .studyHallThumbnail(studyHall.getThumbnail()) + .openTime(studyHall.getOpenTime()) + .closeTime(studyHall.getCloseTime()) .available(true) // 기본값, 서비스에서 계산 .currentReservations(0) // 기본값, 서비스에서 계산 .build();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (24)
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomController.java(0 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/studyRoomMap.http(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/request/StudyHallSearchRequest.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/TimeSlotResponse.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoom.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/exception/LocationServiceException.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyRoomReservationRepository.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java(4 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomReservationService.java(0 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java(5 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java(1 hunks)src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java(3 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomReservationService.java
- src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomController.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: reservationdetaildto와 같은 response dto에서는 @DaTa 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다....
Learnt from: Roysung0625
PR: Please-Hug/HugmeEXP_Backend#32
File: src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationDetailDto.java:5-5
Timestamp: 2025-07-23T09:00:11.298Z
Learning: ReservationDetailDto와 같은 response DTO에서는 Data 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다.
Applied to files:
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
🧬 Code Graph Analysis (4)
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
DistanceCalculator(9-38)
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
StudyRoomConstants(8-52)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
StudyRoomConstants(8-52)
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
StudyRoomConstants(8-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (22)
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoom.java (1)
13-19: 복합 인덱스 설계가 적절합니다.스터디홀별 룸 조회 최적화를 위한 복합 인덱스 설계가 좋습니다. 컬럼 순서도 일반적인 쿼리 패턴(
studyhall_id→is_deleted→max_num)에 맞게 잘 구성되어 있어 WHERE 절과 ORDER BY 절에서 효율적으로 활용될 것으로 예상됩니다.src/main/java/org/example/hugmeexp/domain/studyRoom/exception/LocationServiceException.java (1)
7-9: 예외 생성자 단순화가 적절합니다.불필요한 정수 상태 코드 매개변수를 제거하고 메시지 형식을 표준화한 변경이 좋습니다. 위치 서비스 관련 예외를 일관되게 처리할 수 있게 되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/TimeSlotResponse.java (1)
26-34: 입력 검증 로직이 잘 구현되었습니다.시간 슬롯 생성 시 필수 매개변수의 null 체크와 시간 순서 검증을 추가한 것이 좋습니다. 방어적 프로그래밍을 통해 런타임 에러를 사전에 방지할 수 있게 되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.java (1)
18-31: 성능 최적화가 잘 적용되었습니다.
DateTimeFormatter를 static final로 선언하여 객체 생성 비용을 줄이고 메모리 사용량을 최적화한 변경이 좋습니다.DateTimeFormatter는 불변 객체이므로 스레드 안전성도 보장됩니다.src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
21-37: 거리 계산 로직이 올바르게 구현되었습니다.Haversine 공식을 사용한 거리 계산과 null 안전성 처리, 소수점 둘째 자리까지의 반올림 로직이 모두 적절하게 구현되어 있습니다. 중앙화된 유틸리티 클래스로 코드 재사용성과 유지보수성이 향상되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
8-8: 상수 클래스 설계가 적절합니다.final 클래스와 private constructor를 사용하여 인스턴스화와 상속을 올바르게 방지했습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java (2)
11-11: 임포트 추가가 적절합니다.DistanceCalculator 유틸리티 클래스 사용을 위한 임포트가 올바르게 추가되었습니다.
78-83: 거리 계산 로직 위임이 우수한 리팩토링입니다.인라인 Haversine 공식 구현을 중앙화된 유틸리티 클래스로 위임하여 코드 재사용성과 유지보수성을 크게 개선했습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java (2)
17-25: 서비스 클래스 구조가 깔끔합니다.@service, @requiredargsconstructor, @slf4j 어노테이션 조합과 생성자 주입 방식이 Spring 모범 사례에 부합합니다.
29-45: 초기화 로직이 견고하게 구현되었습니다.ApplicationReadyEvent 사용으로 적절한 시점에 동기화를 수행하며, try-catch 블록으로 실패시 애플리케이션 시작을 방해하지 않는 안전한 설계입니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyRoomReservationRepository.java (2)
86-92: 날짜 범위 쿼리로의 개선이 우수합니다.단일 날짜 필터링에서 LocalDateTime 범위 기반 쿼리로 변경하여 더 정확하고 유연한 예약 조회가 가능해졌습니다. 메서드명도 기능을 명확히 반영합니다.
86-87: 쿼리 조건의 경계값 처리가 정확합니다.
>=와<를 사용하여 시작 시점은 포함하고 종료 시점은 제외하는 반개구간 처리가 올바릅니다. 이는 날짜 범위 쿼리의 표준적인 방식입니다.src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java (2)
7-7: 임포트 추가가 적절합니다.StudyHall 임포트 추가로 null 체크 로직에서 사용할 수 있습니다.
40-56: Null safety 처리가 견고하게 구현되었습니다.StudyHall이 null인 경우에 대한 방어적 프로그래밍이 잘 구현되었습니다. 의미있는 기본값들("정보 없음", "스터디홀 정보를 불러올 수 없습니다.")을 제공하여 사용자 경험을 개선했습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java (1)
12-18: 인덱스 추가가 적절합니다시간 기반 예약 검색을 위한 복합 인덱스가 잘 설계되었습니다. 쿼리 성능 향상에 도움이 될 것입니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java (1)
135-138: 상수 활용 리팩토링 잘 되었습니다
StudyRoomConstants를 활용한 리팩토링이 잘 되었습니다. 코드의 일관성과 유지보수성이 향상되었습니다.Also applies to: 187-187, 198-198
src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java (1)
40-74: Optional 타입 사용 개선 👍null 대신 Optional을 반환하도록 변경한 것은 명시적인 absence 처리를 위한 좋은 개선입니다.
src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java (1)
94-97: Jackson DefaultTyping 설정 검토 필요
DefaultTyping.NON_FINAL활성화는 의도치 않은 클래스 역직렬화를 허용해 보안 취약점(RCE 등)을 초래할 수 있습니다.
해당 ObjectMapper가 실제 어떤 RedisTemplate/Serializer에 적용되는지, 외부(신뢰되지 않은) 데이터가 역직렬화 대상인지 아래 위치에서 확인 후 대응 방안을 검토하세요:
- src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java
createObjectMapper메서드 (94–97라인)권장 대응
PolymorphicTypeValidator로 허용 클래스 범위 제한- 필요 시 기본 typing 대신 명시적 서브타입 지정
- 외부 입력 데이터 검증 로직 추가
위 사항 확인 후 보안 조치 여부를 결정해주세요.
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java (2)
19-30: 인덱스 최적화 개선 👍검색 및 위치 기반 쿼리를 위한 복합 인덱스 추가는 성능 향상에 도움이 됩니다.
111-131: 운영 시간 로직 개선 👍24시간 운영 및 자정을 넘는 운영 시간을 올바르게 처리하는 로직 개선이 잘 되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java (2)
86-111: 적절한 예외 처리 구현하이브리드 검색 메서드가 적절한 예외 처리와 일관된 응답 형식을 사용하고 있습니다.
280-308: 날짜 검증 및 예외 처리가 잘 구현됨날짜 형식 검증과 구체적인 오류 메시지를 포함한 예외 처리가 적절히 구현되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java
Outdated
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java
Outdated
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/studyRoomMap.http
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java
Outdated
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
src/test/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallServiceTest.java (1)
42-64: Redis 동기화 및 좌표 검증 로직 테스트 누락
createStudyHall메서드가 이제 Redis 동기화와 좌표 검증을 포함하고 있지만, 테스트에서는 이를 검증하지 않습니다:
syncToRedis호출 여부 확인 누락- 위도/경도 검증 로직 테스트 누락
KakaoMapService상호작용 테스트 누락다음과 같은 테스트를 추가하는 것을 권장합니다:
@Test @DisplayName("스터디 홀 생성 성공") void createStudyHall_success() { // given StudyHallRequest requestDto = StudyHallRequest.builder() .name("테스트 홀") .description("테스트 설명입니다.") .simpleAddress("서울시 강남구") + .latitude(37.5665) + .longitude(126.9780) .build(); // when studyHallService.createStudyHall(requestDto); // then ArgumentCaptor<StudyHall> captor = ArgumentCaptor.forClass(StudyHall.class); verify(studyHallRepository).save(captor.capture()); StudyHall savedHall = captor.getValue(); assertNotNull(savedHall); assertEquals("테스트 홀", savedHall.getName()); assertEquals("테스트 설명입니다.", savedHall.getDescription()); assertEquals("서울시 강남구", savedHall.getSimpleAddress()); + // Redis 동기화 관련 검증 추가 필요 } +@Test +@DisplayName("잘못된 좌표로 스터디 홀 생성 실패") +void createStudyHall_fail_invalidCoordinates() { + // 한국 범위 밖의 좌표로 테스트 +}
♻️ Duplicate comments (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java (1)
64-66: 데이터 일관성 문제가 여전히 존재합니다이전 리뷰에서 지적된 Redis 동기화 실패 시 데이터 불일치 문제가 아직 해결되지 않았습니다. DB와 Redis 간의 데이터 일관성을 보장하기 위한 메커니즘이 필요합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java(3 hunks)src/test/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallServiceTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: reservationdetaildto와 같은 response dto에서는 @DaTa 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다....
Learnt from: Roysung0625
PR: Please-Hug/HugmeEXP_Backend#32
File: src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationDetailDto.java:5-5
Timestamp: 2025-07-23T09:00:11.298Z
Learning: ReservationDetailDto와 같은 response DTO에서는 Data 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다.
Applied to files:
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (2)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java (2)
138-145: 좌표 검증 로직 구현 우수좌표 검증을 별도 메서드로 분리하고 상수를 중앙화한 것은 좋은 설계입니다. null 체크를 통한 선택적 좌표 처리도 적절합니다.
147-161: Response DTO 어노테이션 확인 – 문제 없음
StudyHallLocationResponse에@Data가 아닌@Getter및@Builder(toBuilder = true)만 사용하고 있어 팀 컨벤션에 부합합니다.- 별도 수정이 필요하지 않습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
Show resolved
Hide resolved
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
Show resolved
Hide resolved
src/test/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallServiceTest.java
Outdated
Show resolved
Hide resolved
5a6fbbd to
3bbafd2
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (16)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java (1)
32-32: N+1 쿼리 최적화 확인이 필요합니다.
findAllWithStudyRooms()메서드가 StudyRoom과의 관계를 적절히 fetch join하고 있는지 확인하세요. 애플리케이션 시작 시 모든 스터디홀을 로드할 때 성능 문제가 발생할 수 있습니다.src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
46-47: 메시지 포맷 국제화를 고려해보세요.한국어로 하드코딩된 메시지 포맷은 국제화에 제한적입니다.
메시지 국제화를 위해 메시지 키 상수로 변경하거나, 별도의 메시지 프로퍼티 파일 사용을 고려해보세요:
-public static final String SUCCESS_MESSAGE_FORMAT = "%s이(가) 성공적으로 %s되었습니다."; -public static final String SEARCH_RESULT_MESSAGE_FORMAT = "%s으로 %d개의 결과를 찾았습니다."; +public static final String SUCCESS_MESSAGE_KEY = "success.message"; +public static final String SEARCH_RESULT_MESSAGE_KEY = "search.result.message";src/main/java/org/example/hugmeexp/domain/studyRoom/controller/studyRoomMap.http (1)
125-142: 하드코딩된 날짜 사용 문제가 여전히 존재합니다.이전 리뷰에서 지적된 하드코딩된 날짜 문제가 여전히 남아있어 테스트 유지보수성이 떨어집니다.
동적 날짜 생성을 사용하면 테스트 유지보수가 개선됩니다:
@today = {{$datetime "yyyy-MM-dd"}} @tomorrow = {{$datetime "yyyy-MM-dd" 1 d}} ### 15. 예약 가능한 시간 조회 (오늘) GET {{baseUrl}}/rooms/1/available-times?date={{today}} ### 16. 예약 가능한 시간 조회 (내일) GET {{baseUrl}}/rooms/1/available-times?date={{tomorrow}}src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java (1)
172-176: 더 구체적인 예외 사용을 권장합니다.룸이 홀에 속하지 않는 경우에 대한 예외 처리가 이전 리뷰에서 지적된 것과 동일한 문제를 가지고 있습니다.
private void validateRoomBelongsToHall(StudyRoom studyRoom, StudyHall studyHall) { if (!studyRoom.getStudyHall().getId().equals(studyHall.getId())) { - throw new StudyRoomNotFoundException(studyRoom.getId()); + throw new IllegalArgumentException( + String.format("스터디룸 %d는 스터디홀 %d에 속하지 않습니다", + studyRoom.getId(), studyHall.getId())); } }src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java (1)
58-60: 모니터링 시스템 연동 필요예외 발생 시 로그만 남기고 있어 프로덕션 환경에서 문제 추적이 어려울 수 있습니다.
} catch (Exception e) { log.error("스터디홀 {} 검색어 인덱싱 실패", studyHall.getId(), e); + // 모니터링 시스템에 알림 + // metricsService.incrementRedisIndexingFailure(); }src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java (1)
98-116: 중복 코드 제거 필요
searchNearbyStudyHalls와searchNearbyStudyHallsWithRedis메서드가 동일한 로직을 가지고 있습니다.public List<StudyHallLocationResponse> searchNearbyStudyHalls(StudyHallSearchRequest request) { - try { - List<StudyHallLocationResponse> redisResults = redisGeoService.findNearbyStudyHalls( - request.getLatitude(), - request.getLongitude(), - request.getRadius(), - request.getLimit() - ); - - if (!redisResults.isEmpty()) { - return redisResults; - } - } catch (Exception e) { - log.debug("Redis Geo 검색 실패, DB 검색으로 fallback"); - } - - // DB Fallback - return searchNearbyStudyHallsWithDB(request); + return searchNearbyStudyHallsWithRedis(request); }src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java (1)
127-127: 주석 띄어쓰기 수정 필요- // 다음날로 넘어가는 경우 (예: 22:00 - 02:00) + // 다음 날로 넘어가는 경우 (예: 22:00 - 02:00)src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java (1)
161-166: 페이지네이션 또는 필터링 고려 필요모든 스터디홀을 한 번에 로드하는 것은 데이터 증가 시 성능 문제를 야기할 수 있습니다.
지역 기반 필터링이나 페이지네이션 도입을 고려해보세요:
// 예시: 바운딩박스 기반 필터링 public List<StudyHallLocationResponse> getStudyHallsInBounds( Double minLat, Double maxLat, Double minLng, Double maxLng) { List<StudyHall> studyHalls = studyHallRepository .findAllWithinBounds(minLat, maxLat, minLng, maxLng); return studyHalls.stream() .map(StudyHallLocationResponse::from) .toList(); }src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java (2)
37-74: 좌표 검증 로직이 잘 구현되었습니다!이전 리뷰에서 제안한 한국 지역 범위 검증이 적절히 구현되었습니다.
150-170: 재색인 시 데이터 정리 개선 확인 및 원자적 교체 필요Hash 데이터 정리가 적절히 구현되었습니다. 하지만 여전히 재색인 중 검색 요청이 빈 결과를 반환할 수 있는 문제가 있습니다.
임시 키를 사용한 원자적 교체 방식을 고려하세요:
public void reindexAllStudyHalls(List<StudyHall> studyHalls) { try { + String GEO_KEY_TMP = GEO_KEY + ":tmp"; + String HALL_DATA_KEY_TMP = HALL_DATA_KEY + "tmp:"; + + // 임시 키에 새 데이터 색인 + for (StudyHall hall : studyHalls) { + // 임시 키로 색인하는 로직 + } + + // 원자적으로 교체 + redisTemplate.rename(GEO_KEY_TMP, GEO_KEY); + // 기존 데이터 삭제 - redisTemplate.delete(GEO_KEY); - - // 기존 Hash 데이터도 모두 삭제 - Set<String> hashKeys = redisTemplate.keys(HALL_DATA_KEY + "*"); - if (hashKeys != null && !hashKeys.isEmpty()) { - redisTemplate.delete(hashKeys); - } - - // 새 데이터 색인 - for (StudyHall hall : studyHalls) { - indexStudyHallLocation(hall); - }src/main/java/org/example/hugmeexp/domain/studyRoom/dto/request/StudyHallSearchRequest.java (1)
146-162: 캐시 키 생성이 여전히 불완전합니다캐시 키에
minCapacity,maxCapacity,searchDateTime,timeZoneId필드가 누락되어 있어, 동일한 위치/키워드지만 다른 조건을 가진 요청이 잘못된 캐시 결과를 반환할 수 있습니다.public String getCacheKey() { if (hasValidLocationInfo()) { - return String.format("nearbyHalls:lat:%s:lng:%s:radius:%s:limit:%s", + return String.format("nearbyHalls:lat:%s:lng:%s:radius:%s:limit:%s:minCap:%s:maxCap:%s:time:%s:tz:%s", Objects.toString(latitude, ""), Objects.toString(longitude, ""), Objects.toString(radius, ""), - Objects.toString(limit, "")); + Objects.toString(limit, ""), + Objects.toString(minCapacity, ""), + Objects.toString(maxCapacity, ""), + Objects.toString(searchDateTime, ""), + Objects.toString(timeZoneId, "")); } if (hasValidKeyword()) { - return String.format("keywordSearch:%s:limit:%s", + return String.format("keywordSearch:%s:limit:%s:minCap:%s:maxCap:%s:time:%s:tz:%s", Objects.toString(keyword, ""), - Objects.toString(limit, "")); + Objects.toString(limit, ""), + Objects.toString(minCapacity, ""), + Objects.toString(maxCapacity, ""), + Objects.toString(searchDateTime, ""), + Objects.toString(timeZoneId, "")); }src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java (3)
100-112: Redis 동기화 실패 로깅 수준 개선 필요Redis 동기화 실패를 debug 레벨로만 로깅하면 프로덕션 환경에서 문제를 감지하기 어렵습니다.
private void syncToRedis(StudyHall studyHall) { try { if (studyHall != null && studyHall.getId() != null) { redisGeoService.indexStudyHallLocation(studyHall); autoCompleteService.indexSearchTerms(studyHall); } } catch (Exception e) { // Redis 실패해도 DB는 정상 처리됨, 조용히 로그만 - log.debug("Redis 동기화 실패 (DB는 정상 처리됨) - StudyHall: {}", + log.error("Redis 동기화 실패 (DB는 정상 처리됨) - StudyHall: {}, Error: {}", - studyHall != null ? studyHall.getId() : "null"); + studyHall != null ? studyHall.getId() : "null", e.getMessage()); } }
114-120: Redis에서 불완전한 데이터 제거
RedisAutoCompleteService에서 데이터를 제거하지 않아 삭제된 스터디홀이 자동완성 검색에 계속 나타날 수 있습니다.private void removeFromRedis(Long studyHallId) { try { redisGeoService.removeStudyHallLocation(studyHallId); + autoCompleteService.removeSearchTerms(studyHallId); } catch (Exception e) { - log.debug("Redis 삭제 실패 (DB는 정상 처리됨) - StudyHall: {}", studyHallId); + log.error("Redis 삭제 실패 (DB는 정상 처리됨) - StudyHall: {}, Error: {}", + studyHallId, e.getMessage()); } }
73-85: 불필요한 변수 할당
save()호출이 제거되어 좋습니다. 하지만updatedHall변수가 불필요합니다.@Transactional @CacheEvict(value = "studyHalls", key = "#studyHallId") public StudyHall updateStudyHall(Long studyHallId, StudyHallRequest requestDto) { StudyHall studyHall = findStudyHallById(studyHallId); studyHall.update(requestDto); - StudyHall updatedHall = studyHall; - // Redis 동기화 (조용히 처리) - syncToRedis(updatedHall); + syncToRedis(studyHall); - return updatedHall; + return studyHall; }src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java (2)
186-200: Legacy API가 여전히 Redis 기반 메서드를 호출합니다Legacy/Fallback API로 표시되어 있지만 Redis 기반
getSmartAutocompleteSuggestions를 호출하고 있어 Redis 장애 시 fallback이 작동하지 않습니다.@Operation(summary = "기본 자동완성 (Legacy)", description = "기존 DB 기반 자동완성 (Redis 장애시 Fallback용)") @GetMapping("/search/autocomplete") public ResponseEntity<Response<List<String>>> getBasicAutocompleteSuggestions( @Parameter(description = "자동완성 검색어", required = true) @RequestParam String query, @Parameter(description = "제안 개수", example = "5") @RequestParam(defaultValue = "5") Integer limit) { - List<String> suggestions = studyHallSearchService.getSmartAutocompleteSuggestions(query, limit); + // DB 기반 fallback 메서드를 호출해야 함 + List<String> suggestions = studyHallSearchService.getBasicAutocompleteSuggestions(query, limit); return ResponseEntity.ok(Response.<List<String>>builder() .message(String.format("%d개의 자동완성 제안을 찾았습니다.", suggestions.size())) .data(suggestions) .build()); }
375-385: 입력 검증 및 예외 처리 누락이름 검색 API에 입력 검증과 예외 처리가 누락되어 있습니다.
@Operation(summary = "이름으로 스터디홀 검색 (Legacy)") @GetMapping("/search/name") public ResponseEntity<Response<List<StudyHallLocationResponse>>> searchStudyHallsByName( @RequestParam String name) { - List<StudyHallLocationResponse> studyHalls = studyHallSearchService.searchStudyHallsByName(name); - return ResponseEntity.ok(Response.<List<StudyHallLocationResponse>>builder() - .message(String.format("이름 '%s'로 %d개의 스터디홀을 찾았습니다.", name, studyHalls.size())) - .data(studyHalls) - .build()); + try { + if (name == null || name.trim().isEmpty()) { + return ResponseEntity.badRequest() + .body(Response.<List<StudyHallLocationResponse>>builder() + .message("검색할 이름을 입력해주세요.") + .data(List.of()) + .build()); + } + + List<StudyHallLocationResponse> studyHalls = studyHallSearchService.searchStudyHallsByName(name); + return ResponseEntity.ok(Response.<List<StudyHallLocationResponse>>builder() + .message(String.format("이름 '%s'로 %d개의 스터디홀을 찾았습니다.", name, studyHalls.size())) + .data(studyHalls) + .build()); + } catch (Exception e) { + log.error("이름 검색 중 오류 발생 - name: {}", name, e); + return ResponseEntity.internalServerError() + .body(Response.<List<StudyHallLocationResponse>>builder() + .message("검색 중 오류가 발생했습니다.") + .data(List.of()) + .build()); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (26)
src/main/java/org/example/hugmeexp/domain/studyRoom/config/StudyRoomRedisConfig.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomController.java(0 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/controller/studyRoomMap.http(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/request/StudyHallSearchRequest.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/TimeSlotResponse.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java(2 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoom.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/exception/LocationServiceException.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyHallRepository.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyRoomReservationRepository.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java(4 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java(3 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomReservationService.java(0 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java(5 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java(1 hunks)src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java(1 hunks)src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java(3 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomReservationService.java
- src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomController.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: reservationdetaildto와 같은 response dto에서는 @DaTa 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다....
Learnt from: Roysung0625
PR: Please-Hug/HugmeEXP_Backend#32
File: src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationDetailDto.java:5-5
Timestamp: 2025-07-23T09:00:11.298Z
Learning: ReservationDetailDto와 같은 response DTO에서는 Data 어노테이션을 사용하지 않고, 사용하지 않는 lombok import는 제거하는 것을 선호한다.
Applied to files:
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.javasrc/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java
🧬 Code Graph Analysis (3)
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (1)
StudyRoomConstants(8-52)
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
DistanceCalculator(9-36)
src/main/java/org/example/hugmeexp/domain/studyRoom/config/StudyRoomRedisConfig.java (1)
src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java (1)
Configuration(21-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (29)
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoom.java (1)
13-19: 복합 인덱스 최적화가 적절합니다.스터디홀별 룸 조회를 위한 복합 인덱스
idx_study_room_by_hall이 잘 설계되었습니다.studyhall_id, is_deleted, max_num순서로 구성된 인덱스는 스터디홀별 필터링, 삭제 상태 확인, 최대 인원 범위 쿼리를 효율적으로 지원할 것입니다.src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyHallRepository.java (1)
95-102: Redis 동기화용 메서드가 잘 구현되었습니다.
LEFT JOIN FETCH를 사용하여 N+1 문제를 해결한 효율적인 메서드입니다. Redis 동기화 시나리오에 적합한 구현이며, 메서드명과 문서화도 명확합니다.다만 모든 스터디홀과 룸 데이터를 한 번에 로드하므로, 대용량 데이터셋에서는 메모리 사용량에 주의가 필요합니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/exception/LocationServiceException.java (1)
8-8: 생성자 단순화가 적절합니다.
HttpStatus.BAD_REQUEST가 이미 400 상태 코드를 나타내므로 중복 매개변수를 제거한 것이 좋은 리팩터링입니다. 코드가 더 깔끔해지고 중복이 제거되었습니다.src/main/java/org/example/hugmeexp/domain/studyRoom/entity/Location.java (2)
11-11: 유틸리티 클래스 도입이 적절합니다.
78-83: 거리 계산 로직 중앙화가 훌륭합니다.
DistanceCalculator로 거리 계산 로직을 위임한 리팩터링이 좋습니다. DRY 원칙을 따르며, null 처리와 반올림 로직이 일관되게 적용됩니다. 코드 중복을 제거하고 유지보수성을 향상시켰습니다.src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/TimeSlotResponse.java (1)
26-34: 입력 유효성 검증 추가가 훌륭합니다.팩터리 메서드에 null 체크와 시간 순서 검증을 추가한 것이 좋습니다. fail-fast 원칙을 따르며, 명확한 한국어 에러 메시지로 사용자 친화적입니다. 잘못된 TimeSlot 객체 생성을 사전에 방지할 수 있어 시스템 안정성이 향상되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/ReservationTimeResponse.java (1)
17-19: 성능 최적화가 올바르게 적용되었습니다.
DateTimeFormatter를 static final로 선언하여 인스턴스 재사용을 통한 성능 개선이 적절히 구현되었습니다. 스레드 안전하며 메모리 효율적입니다.Also applies to: 28-31
src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java (1)
40-56: null 안전성 개선이 적절히 구현되었습니다.StudyHall이 null인 경우에 대한 방어적 처리가 올바르게 추가되었습니다. 기본값 설정과 함께 API 계약을 유지하면서 안정성을 크게 향상시켰습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/util/DistanceCalculator.java (1)
1-36: 거리 계산 유틸리티 클래스가 올바르게 구현되었습니다.Haversine 공식을 사용한 거리 계산이 정확하게 구현되었고, null 안전성과 적절한 정밀도 처리가 포함되어 있습니다. 중앙화된 상수 사용으로 일관성도 확보되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java (1)
30-45: 예외 처리와 서비스 구조가 적절합니다.Redis 동기화 실패 시 애플리케이션 시작을 중단하지 않고 DB fallback으로 graceful 처리하는 전략이 올바릅니다. 로깅도 적절히 구현되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/repository/StudyRoomReservationRepository.java (1)
84-92: 쿼리 최적화가 효과적으로 적용되었습니다.DATE() 함수 대신 범위 기반 필터링으로 변경하여 인덱스 활용도를 높이고 데이터베이스 성능을 개선했습니다. 메서드명도 실제 기능을 더 정확히 반영합니다.
src/main/java/org/example/hugmeexp/global/common/config/RedisConfig.java (1)
34-34: @primary 어노테이션 추가가 적절합니다.새로운 Redis 설정 클래스들과의 빈 충돌을 방지하기 위한 @primary 어노테이션 추가가 적절합니다. 이는 StudyRoomRedisConfig의 전용 RedisTemplate들과 잘 구분됩니다.
Also applies to: 68-68
src/main/java/org/example/hugmeexp/domain/studyRoom/constants/StudyRoomConstants.java (3)
26-29: 한국 좌표 범위가 정확하게 수정되었습니다.이전 리뷰 피드백에 따라 한국의 실제 영토 범위를 정확히 반영한 좌표값으로 업데이트되었습니다. 이는 지리적 검색의 정확성을 향상시킵니다.
33-33: 거리 계산 상수가 정확하게 수정되었습니다.위도 1도당 거리를 111.32km로 정확히 수정하여 거리 계산의 정밀도가 개선되었습니다.
8-8: 상수 클래스 구조가 우수합니다.final 클래스 선언과 private 생성자를 통한 인스턴스화 방지는 유틸리티 클래스의 모범 사례를 잘 따르고 있습니다.
Also applies to: 49-51
src/main/java/org/example/hugmeexp/domain/studyRoom/config/StudyRoomRedisConfig.java (2)
22-23: 전용 Redis 설정 분리가 우수한 설계입니다.StudyRoom 도메인의 특화된 Redis 요구사항(Geo, Trie)을 별도 설정 클래스로 분리한 것은 단일 책임 원칙을 잘 따르는 설계입니다.
30-46: Geo 전용 RedisTemplate 구성이 적절합니다.위치 데이터 직렬화에 특화된 RedisTemplate과 명확한 빈 이름 지정이 좋습니다. 사용 목적이 명확히 문서화되어 있어 유지보수성도 우수합니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/studyRoomMap.http (1)
19-63: 새로운 Redis 기반 API 테스트 구성이 우수합니다.Redis Geo, Trie 자동완성, 하이브리드 검색 등 새로운 기능들을 체계적으로 테스트할 수 있도록 잘 구성되어 있습니다. Legacy API와의 구분도 명확합니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java (2)
4-4: 상수 활용을 통한 설정 중앙화가 우수합니다.StudyRoomConstants를 활용하여 기본 운영시간, 시간 슬롯 간격, 예약 기간 등을 중앙에서 관리할 수 있게 되어 유지보수성이 크게 향상되었습니다.
Also applies to: 135-138, 187-187, 198-198
181-194: 시간 슬롯 생성 로직 분리가 효과적입니다.시간 슬롯 생성 로직을 별도 메서드로 추출하여 코드 가독성과 재사용성이 향상되었습니다. 설정 가능한 간격을 사용하는 것도 좋은 개선점입니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java (1)
12-18: 인덱스 추가가 적절합니다예약 시간 기반 검색을 위한 복합 인덱스 추가가 성능 최적화에 도움이 될 것입니다.
studyroom_id,reservation_start,reservation_end컬럼 순서도 적절합니다.src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java (2)
183-198: SCAN 사용으로 성능 개선 확인이전 리뷰에서 지적된
keys()명령어 대신 SCAN을 사용하여 Redis 블로킹 문제를 해결했습니다. 배치 크기 1000도 적절합니다.
148-153: 키워드 정규화 로직 적절한글, 영문, 숫자, 공백만 남기고 특수문자를 제거하는 정규식이 올바르게 구현되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java (2)
42-43: 캐시 키 생성 개선 확인이전 리뷰에서 지적된
toString()대신 명시적인cacheKey속성을 사용하도록 개선되었습니다.
206-218: 바운딩박스 계산 로직 적절위도에 따른 경도 변화를 고려한 정확한 바운딩박스 계산이 구현되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java (2)
19-30: 검색 최적화를 위한 인덱스 추가 적절검색 쿼리와 위치 기반 쿼리를 위한 복합 인덱스가 적절하게 설계되었습니다. 컬럼 순서도 쿼리 패턴에 맞게 구성되었습니다.
111-131: 운영 시간 체크 로직 개선24시간 운영과 자정을 넘는 운영 시간을 올바르게 처리하도록 개선되었습니다.
src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java (1)
40-73: Optional 사용으로 null 안전성 향상null 대신 Optional을 반환하도록 개선되어 명시적인 값 부재 처리가 가능해졌습니다.
Also applies to: 79-111
src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java (1)
43-72: 예외 처리가 적절히 구현되었습니다!이전 리뷰에서 지적한 예외 처리가 잘 추가되었습니다.
🚀 What’s this PR about?
redis geo+trie 를 도입하여 지도에서 스터디홀을 검색할 시 약 5배 빠르게, 검색에서 자동환성이 약 10~20배 빠르게 개선하였습니다.
이외 자잘한 성능개선 리팩토링과 버그를 수정하였습니다.
관련된 이슈를 연결하세요. (예: closes #이슈번호)
closes [리팩토링] 스터디룸 지도 기능 성능개선 리팩토링 #67
Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
성능 개선
문서
기타