Skip to content

[성능개선/버그] 스터디룸 지도 redis geo+tire 도입, 성능개선+버그수정#79

Open
jam9582 wants to merge 16 commits intodevelopfrom
feature/#67-studyroom-map-refactoring
Open

[성능개선/버그] 스터디룸 지도 redis geo+tire 도입, 성능개선+버그수정#79
jam9582 wants to merge 16 commits intodevelopfrom
feature/#67-studyroom-map-refactoring

Conversation

@jam9582
Copy link
Contributor

@jam9582 jam9582 commented Aug 6, 2025

🚀 What’s this PR about?

  • redis geo+trie 를 도입하여 지도에서 스터디홀을 검색할 시 약 5배 빠르게, 검색에서 자동환성이 약 10~20배 빠르게 개선하였습니다.

  • 이외 자잘한 성능개선 리팩토링과 버그를 수정하였습니다.

  • 관련된 이슈를 연결하세요. (예: closes #이슈번호)
    closes [리팩토링] 스터디룸 지도 기능 성능개선 리팩토링 #67

Summary by CodeRabbit

  • 신규 기능

    • 스터디룸/스터디홀 위치 기반 검색, 키워드 자동완성, 하이브리드 검색 등 고성능 Redis 기반 검색 API 추가
    • Redis 자동완성, 인기 키워드, 위치 데이터 동기화 및 인덱싱 기능 도입
  • 기능 개선

    • 검색 요청 시 위치, 키워드, 수용 인원, 시간 등 다양한 조건 지원 및 검증 강화
    • 지도 및 검색 관련 API의 입력값 검증, 예외 처리, 표준화된 메시지 적용
    • 스터디홀 운영시간 24시간/심야 운영 등 다양한 케이스 처리 로직 개선
    • 예약 시간대 생성 및 검증 로직을 상수 기반으로 일반화
  • 버그 수정

    • 스터디룸/스터디홀 정보 누락 시 기본값 제공 및 예외 발생 방지
  • 성능 개선

    • DB 인덱스 구조 개선(복합 인덱스 추가) 및 예약 검색 효율화
  • 문서

    • HTTP API 테스트 컬렉션에 Redis 기반 신규/레거시 엔드포인트 및 다양한 테스트 케이스 추가
  • 기타

    • 내부 코드 리팩터링 및 유틸리티 클래스 도입, 불필요한 코드/메서드 제거

@jam9582 jam9582 self-assigned this Aug 6, 2025
Copilot AI review requested due to automatic review settings August 6, 2025 23:43
@coderabbitai
Copy link

coderabbitai bot commented Aug 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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

Cohort / File(s) Change Summary
StudyRoom 도메인 상수 및 유틸리티 추가
.../constants/StudyRoomConstants.java, .../util/DistanceCalculator.java
도메인 전역 상수 클래스와 하버사인 거리 계산 유틸리티 클래스 신규 추가
Redis 기반 검색/동기화 서비스 도입
.../service/redis/RedisGeoService.java, .../service/redis/RedisAutoCompleteService.java, .../service/redis/RedisDataSyncService.java, .../config/StudyRoomRedisConfig.java
Redis Geo/Trie 기반 위치검색, 오토컴플리트, 전체 동기화 서비스 및 RedisTemplate 설정 신규 도입
검색 서비스 분리 및 성능개선
.../service/StudyHallSearchService.java, .../service/StudyHallService.java, .../service/KakaoMapService.java
검색 전용 서비스 신설, 위치/키워드/하이브리드 검색, Redis-DB 페일오버, 거리계산 로직 분리 및 성능 개선, 캐시 동기화 로직 추가
컨트롤러 리팩토링
.../controller/StudyRoomMapController.java, .../controller/StudyRoomController.java
Redis 기반 신규 API 및 입력 검증, 예외 처리, 응답 표준화, 기존 컨트롤러 제거
DTO/응답/요청 객체 확장 및 검증
.../dto/request/StudyHallSearchRequest.java, .../dto/response/ReservationTimeResponse.java, .../dto/response/StudyRoomDetailResponse.java, .../dto/response/TimeSlotResponse.java
검색 요청 필드 확장(키워드, 용량, 날짜, 타임존), 검증 및 캐시키 생성, 응답 객체의 null 안전성, 시간 포맷터 및 유효성 검증 추가
엔티티 및 레포지토리 인덱스 최적화
.../entity/StudyHall.java, .../entity/StudyRoom.java, .../entity/StudyRoomReservation.java, .../repository/StudyHallRepository.java, .../repository/StudyRoomReservationRepository.java, .../entity/Location.java
인덱스 구조 개선, 거리계산 위임, 예약 조회 쿼리 범위 조건화, StudyHall의 isOpen 로직 개선, 동기화용 fetch join 메서드 추가
기타/코드 정리
.../service/StudyRoomReservationService.java, .../exception/LocationServiceException.java, .../global/infra/auth/service/TokenService.java
불필요 import/코드 제거, 예외 생성자 단순화, RedisTemplate 제네릭 명시 등 사소한 정리
API 문서/테스트케이스 확장
.../controller/studyRoomMap.http
Redis 기반 신규 API, 하이브리드 검색, 오토컴플리트 등 테스트케이스 및 문서 보강

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: 응답 반환
Loading
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: 응답 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
파일구조 개선 및 통합 (#67)
거리계산 로직 성능향상 리팩토링 (#67)
이름, 위치 기반 검색 로직 성능향상 리팩토링 (#67)
코드래빗 개선점 수정 (#67)

Assessment against linked issues: Out-of-scope changes

해당 PR의 모든 주요 변경사항은 이슈 #67의 리팩토링, 성능 개선, 구조 개선, 코드 개선 등 요구사항 범위 내에 있습니다. 기능과 무관한 불필요한 코드 변경은 발견되지 않았습니다.

Possibly related issues

  • Please-Hug/HugmeEXP_Frontend#22: 프론트엔드의 지도 및 검색 기능 리팩토링과 직접적으로 연관되어 있어 백엔드의 본 PR과 연동될 수 있습니다.

Possibly related PRs

Suggested labels

기능 개발

Suggested reviewers

  • seongjunin98

Poem

🐇
Redis 바람 타고 방을 찾아
트라이와 지오, 빠르게 달려가
거리 계산 똑똑하게,
검색어도 척척 맞춰주네!
코드도 구조도 한층 더 단단히
오늘도 토끼는 귀를 쫑긋,
더 빠른 방 찾기, 이제 걱정 끝!
🗺️✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a375a4a and 4b99ebc.

📒 Files selected for processing (2)
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java (1 hunks)
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-07T04:57:32.640Z
Learnt from: jam9582
PR: Please-Hug/HugmeEXP_Backend#79
File: src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java:54-60
Timestamp: 2025-08-07T04:57:32.640Z
Learning: User jam9582 prefers to avoid HashMap usage in the RedisGeoService due to concurrency concerns, even for local method-scoped variables.

Applied to files:

  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java
📚 Learning: 2025-07-23T09:00:11.298Z
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/redis/RedisGeoService.java
🧬 Code Graph Analysis (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisGeoService.java (3)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallSearchService.java (2)
  • lombok (236-243)
  • Service (29-244)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java (1)
  • Service (28-164)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisDataSyncService.java (1)
  • Service (17-46)
⏰ 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 (1)
src/main/java/org/example/hugmeexp/domain/studyRoom/service/redis/RedisAutoCompleteService.java (1)

180-196: SCAN을 사용한 안전한 키 삭제 구현 확인

Redis를 블로킹하지 않는 SCAN을 사용하여 키를 삭제하는 올바른 구현입니다. 프로덕션 환경에서 성능 문제를 방지할 수 있습니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#67-studyroom-map-refactoring

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jam9582 jam9582 added 버그 버그를 해결합니다. 리팩토링 구현된 기능을 개선합니다. labels Aug 6, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 도메인 상수 정의 클래스 추가

@github-actions
Copy link

github-actions bot commented Aug 6, 2025

Unit Test Results

359 tests   332 ✔️  14s ⏱️
  48 suites    27 💤
  48 files        0

Results for commit 4b99ebc.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 60a965e and c7d44e0.

📒 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.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyRoomReservation.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java
  • src/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_idis_deletedmax_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: 날짜 검증 및 예외 처리가 잘 구현됨

날짜 형식 검증과 구체적인 오류 메시지를 포함한 예외 처리가 적절히 구현되었습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 동기화와 좌표 검증을 포함하고 있지만, 테스트에서는 이를 검증하지 않습니다:

  1. syncToRedis 호출 여부 확인 누락
  2. 위도/경도 검증 로직 테스트 누락
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between c7d44e0 and 02aca1b.

📒 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)만 사용하고 있어 팀 컨벤션에 부합합니다.
  • 별도 수정이 필요하지 않습니다.

@jam9582 jam9582 force-pushed the feature/#67-studyroom-map-refactoring branch from 5a6fbbd to 3bbafd2 Compare August 7, 2025 04:18
@jam9582 jam9582 added the 성능 개선 구현된 기능의 성능을 개선합니다. label Aug 7, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: 중복 코드 제거 필요

searchNearbyStudyHallssearchNearbyStudyHallsWithRedis 메서드가 동일한 로직을 가지고 있습니다.

 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

📥 Commits

Reviewing files that changed from the base of the PR and between 02aca1b and 3bbafd2.

📒 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.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/KakaoMapService.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyRoomService.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/dto/response/StudyRoomDetailResponse.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/entity/StudyHall.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/service/StudyHallService.java
  • src/main/java/org/example/hugmeexp/domain/studyRoom/controller/StudyRoomMapController.java
  • src/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: 예외 처리가 적절히 구현되었습니다!

이전 리뷰에서 지적한 예외 처리가 잘 추가되었습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

리팩토링 구현된 기능을 개선합니다. 버그 버그를 해결합니다. 성능 개선 구현된 기능의 성능을 개선합니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[리팩토링] 스터디룸 지도 기능 성능개선 리팩토링

4 participants

Comments