[feat] 사용자 맞춤 모임 추천 / 모임 검색(지역, 관심사) / 키워드 + 필터 검색 API / 함께하는 멤버들의 다른 모임 조회 API 구현#86
[feat] 사용자 맞춤 모임 추천 / 모임 검색(지역, 관심사) / 키워드 + 필터 검색 API / 함께하는 멤버들의 다른 모임 조회 API 구현#86ghkddlscks19 merged 19 commits intodevelopfrom
Conversation
…nto feat/search/chan
…into feat/search/chan
…into feat/search/chan
…into feat/search/chan
…into feat/search/chan
Walkthrough이번 변경 사항에서는 모임(클럽) 추천, 관심사/지역/키워드 기반 검색, 함께하는 멤버들이 가입한 모임 조회 등 다양한 검색 및 추천 기능이 신규로 도입되었습니다. 이를 위해 검색 컨트롤러, 서비스, DTO, 레포지토리, 예외 코드가 추가 및 확장되었으며, User 엔티티에 city 필드가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SearchController
participant SearchService
participant ClubRepository
participant UserInterestRepository
participant UserService
User->>SearchController: GET /search/recommendations
SearchController->>SearchService: recommendedClubs(page)
SearchService->>UserService: getCurrentUser()
SearchService->>UserInterestRepository: findInterestIdsByUserId(userId)
SearchService->>ClubRepository: searchByUserInterestAndLocation(...)
alt 결과 없음
SearchService->>ClubRepository: searchByUserInterests(...)
end
SearchService-->>SearchController: List<ClubResponseDto>
SearchController-->>User: ResponseEntity.ok(...)
User->>SearchController: GET /search?keyword=...
SearchController->>SearchService: searchClubs(filterDto)
SearchService->>ClubRepository: searchByKeywordWithFilter(...)
SearchService-->>SearchController: List<ClubResponseDto>
SearchController-->>User: ResponseEntity.ok(...)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 사항 없음) Suggested labels
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
🪧 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.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/main/java/com/example/onlyone/domain/user/entity/User.java (1)
46-48: 사용자 위치 정보를 위한 city 필드 추가가 적절합니다.검색 및 추천 기능을 위한 city 필드가 올바르게 추가되었습니다. 기존 district 필드와 일관된 패턴을 따르고 있습니다.
검색 성능을 위해 city 필드에 데이터베이스 인덱스 추가를 고려해보세요:
CREATE INDEX idx_user_city_district ON user (city, district);src/main/java/com/example/onlyone/domain/search/dto/request/SearchFilterDto.java (1)
43-66: 검증 메서드 로직이 올바르게 구현되었습니다.지역 필터와 키워드 검증 로직이 비즈니스 요구사항과 에러 코드 정의에 맞게 구현되었습니다.
중복된 검증 로직을 줄이기 위해 hasLocation() 메서드를 활용하도록 개선할 수 있습니다:
public boolean isLocationValid() { - if (city == null && district == null) { - return true; // 둘 다 없으면 OK - } - if (city != null && district != null && - !city.trim().isEmpty() && !district.trim().isEmpty()) { - return true; // 둘 다 있으면 OK - } - return false; // 하나만 있으면 Invalid + return (city == null && district == null) || hasLocation(); }src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java (1)
79-102: 복잡한 서브쿼리 성능 최적화 필요이 쿼리는 여러 EXISTS 서브쿼리를 사용하여 성능 문제가 발생할 수 있습니다. 특히 대량의 데이터에서는 인덱스 최적화가 필요합니다.
다음 인덱스들을 고려해보세요:
user_club테이블:(user_id, club_id)복합 인덱스user_club테이블:(club_id, club_role)복합 인덱스또한 쿼리 실행 계획을 확인하여 성능을 모니터링하는 것을 권장합니다.
src/main/java/com/example/onlyone/domain/search/controller/SearchController.java (1)
46-65: 입력 검증 개선 필요
keyword파라미터에 대한 검증이 컨트롤러 레벨에서 누락되어 있습니다. API 문서에는 2글자 이상이어야 한다고 명시되어 있지만, 실제 검증은 서비스 레이어에서만 수행됩니다.public ResponseEntity<?> searchClubs( - @RequestParam String keyword, + @RequestParam @Size(min = 2, message = "검색어는 2글자 이상이어야 합니다") String keyword,또한
@Valid또는@Validated어노테이션을 추가하여 Bean Validation을 활성화하는 것을 고려해보세요.src/main/java/com/example/onlyone/domain/search/service/SearchService.java (2)
41-43: 컬렉션 비어있음 확인 개선
size() > 0대신!isEmpty()를 사용하는 것이 더 관용적입니다.-if(resultList.size() > 0) { +if (!resultList.isEmpty()) {
117-129: 인덱스 기반 매핑의 취약성네이티브 쿼리 결과를 인덱스로 매핑하는 것은 SELECT 절의 순서가 변경되면 쉽게 깨질 수 있습니다.
다음 방안들을 고려해보세요:
@SqlResultSetMapping을 사용하여 매핑을 명시적으로 정의- DTO projection을 사용하는 JPQL 쿼리로 리팩토링
- 최소한 상수를 사용하여 인덱스를 관리
private static final int CLUB_ID_INDEX = 0; private static final int NAME_INDEX = 1; // ... 등
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.gitignore(1 hunks)src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java(1 hunks)src/main/java/com/example/onlyone/domain/search/controller/SearchController.java(1 hunks)src/main/java/com/example/onlyone/domain/search/dto/request/SearchFilterDto.java(1 hunks)src/main/java/com/example/onlyone/domain/search/dto/response/ClubResponseDto.java(1 hunks)src/main/java/com/example/onlyone/domain/search/service/SearchService.java(1 hunks)src/main/java/com/example/onlyone/domain/user/entity/User.java(1 hunks)src/main/java/com/example/onlyone/domain/user/repository/UserInterestRepository.java(1 hunks)src/main/java/com/example/onlyone/global/exception/ErrorCode.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/com/example/onlyone/global/exception/ErrorCode.java (1)
src/main/java/com/example/onlyone/global/exception/ErrorResponse.java (1)
ErrorResponse(11-27)
src/main/java/com/example/onlyone/domain/user/repository/UserInterestRepository.java (1)
src/main/java/com/example/onlyone/domain/user/entity/UserInterest.java (1)
UserInterest(11-31)
🔇 Additional comments (9)
.gitignore (2)
39-41: application.yml/Prod 설정 파일을 완전히 무시해도 되는지 확인 필요
application.yml,src/main/resources/application.yml,src/main/resources/application-prod.yml세 파일을 모두.gitignore에 포함시키면 로컬·CI·배포 환경에서 필수 설정 파일이 누락될 수 있습니다.
특히src/main/resources/application.yml/-prod.yml까지 무시하면 스프링 부트 실행 시 기본 프로퍼티를 읽지 못하니, 샘플용application-example.yml파일을 커밋하고 실제 파일을 무시하는 방식이 더 안전합니다. 의도된 정책인지 한 번 더 검토 부탁드립니다.
43-44:CLAUDE.md무시 추가 LGTM
문서성 파일을 VCS에서 제외하려는 의도로 보이며 문제 없습니다.src/main/java/com/example/onlyone/global/exception/ErrorCode.java (1)
107-110: 새로운 검색 관련 에러 코드 추가가 적절합니다.검색 기능을 위한 에러 코드들이 일관된 패턴으로 추가되었습니다. HTTP 상태 코드, 에러 코드 명명 규칙, 그리고 한국어 메시지 모두 기존 코드베이스의 스타일과 일치합니다.
src/main/java/com/example/onlyone/domain/user/repository/UserInterestRepository.java (1)
11-16: 사용자 관심사 조회를 위한 레포지토리가 올바르게 구현되었습니다.JPQL 쿼리가 엔티티 관계를 올바르게 탐색하고 있으며, 파라미터 바인딩도 적절히 구현되었습니다. Spring Data JPA 규칙을 잘 따르고 있습니다.
src/main/java/com/example/onlyone/domain/search/dto/request/SearchFilterDto.java (2)
20-33: SortType 열거형 구현이 적절합니다.정렬 옵션을 위한 열거형이 한국어 설명과 함께 올바르게 구현되었습니다.
35-41: 방어적 프로그래밍이 잘 적용된 getter 메서드들입니다.페이지 번호의 음수 방지와 정렬 기준의 기본값 설정이 적절하게 구현되었습니다.
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java (1)
56-70: 주의: FULLTEXT 인덱스 확인 및 SELECT 절·파라미터 검증 필요아래 사항을 반드시 점검해주세요:
• DDL/마이그레이션 스크립트에서
name,description컬럼에 FULLTEXT 인덱스가 실제로 생성되어 있는지 확인
– 인덱스가 없으면MATCH…AGAINST구문이 실패합니다.
•SELECT절에city컬럼이 빠져 있어 조회 오류가 발생할 수 있으므로 추가- SELECT c.club_id, c.name, c.description, - c.district, c.club_image, i.category, + SELECT c.club_id, c.name, c.description, c.city, + c.district, c.club_image, i.category,•
sortBy파라미터는 허용된 값(예:LATEST,MEMBER_COUNT)만 전달·검증하여 SQL 인젝션 방지
• 복수의CASE WHEN을 사용한ORDER BY가 의도한 대로 정렬되는지 테스트 및 필요 시 간소화src/main/java/com/example/onlyone/domain/search/controller/SearchController.java (1)
18-22: LGTM!사용자 맞춤 추천 엔드포인트가 적절하게 구현되었습니다.
src/main/java/com/example/onlyone/domain/search/service/SearchService.java (1)
67-96: 검증 로직이 적절합니다지역 필터와 키워드 유효성 검증이 잘 구현되어 있습니다.
trim()처리도 적절합니다.
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
Show resolved
Hide resolved
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
Show resolved
Hide resolved
src/main/java/com/example/onlyone/domain/search/dto/response/ClubResponseDto.java
Show resolved
Hide resolved
src/main/java/com/example/onlyone/domain/search/service/SearchService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java (1)
83-105: 팀원들의 다른 모임 조회 로직이 정확합니다.EXISTS와 NOT EXISTS를 활용한 구현이 논리적으로 정확합니다.
대용량 데이터에서는 성능 최적화를 고려해보세요:
SELECT c, COUNT(DISTINCT uc.user_club_id) as member_count FROM Club c JOIN UserClub teammate_clubs ON c.clubId = teammate_clubs.club.clubId JOIN UserClub my_clubs ON teammate_clubs.user.userId = my_clubs.user.userId LEFT JOIN UserClub my_membership ON c.clubId = my_membership.club.clubId AND my_membership.user.userId = :userId WHERE my_clubs.club.clubId IN ( SELECT club.clubId FROM UserClub WHERE user.userId = :userId ) AND teammate_clubs.user.userId != :userId AND my_membership.user_club_id IS NULL GROUP BY c.clubId ORDER BY member_count DESC, c.createdAt DESC
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: in the onlyone-back project, the clubrepository search methods use list return type instea...
Learnt from: ghkddlscks19
PR: GoormOnlyOne/OnlyOne-Back#86
File: src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java:14-18
Timestamp: 2025-08-06T07:54:25.883Z
Learning: In the OnlyOne-Back project, the ClubRepository search methods use List<Object[]> return type instead of Page<Object[]> even with Pageable parameters. This is intentionally designed for infinite scrolling implementation on the frontend, where only the actual data is needed without pagination metadata.
Applied to files:
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
📚 Learning: onlyone-back 프로젝트의 검색 시스템은 관심사, 지역, 최신순, 멤버순 필터가 병렬적으로 선택 가능하도록 설계되어 있어서, order by 절에서 여러 case 문을 사용...
Learnt from: ghkddlscks19
PR: GoormOnlyOne/OnlyOne-Back#86
File: src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java:66-70
Timestamp: 2025-08-06T08:12:05.918Z
Learning: OnlyOne-Back 프로젝트의 검색 시스템은 관심사, 지역, 최신순, 멤버순 필터가 병렬적으로 선택 가능하도록 설계되어 있어서, ORDER BY 절에서 여러 CASE 문을 사용하여 다중 정렬 조건을 동시에 적용할 수 있습니다.
Applied to files:
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
📚 Learning: onlyone-back 프로젝트에서 검색 시스템의 정렬 기준(최신순/멤버순)은 사용자 경험의 명확성과 예측 가능한 결과를 위해 단일 선택 방식으로 구현하는 것이 더 적합하다고 판단...
Learnt from: ghkddlscks19
PR: GoormOnlyOne/OnlyOne-Back#86
File: src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java:66-70
Timestamp: 2025-08-06T08:15:29.022Z
Learning: OnlyOne-Back 프로젝트에서 검색 시스템의 정렬 기준(최신순/멤버순)은 사용자 경험의 명확성과 예측 가능한 결과를 위해 단일 선택 방식으로 구현하는 것이 더 적합하다고 판단됩니다.
Applied to files:
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
📚 Learning: 사용자 gkdudans는 디버깅 코드(system.out.println)를 제거하고, 중요한 로그만 log.info나 log.warn을 사용하여 적절한 로깅 프레임워크로 남기는 것...
Learnt from: gkdudans
PR: GoormOnlyOne/OnlyOne-Back#60
File: src/main/java/com/example/onlyone/domain/chat/controller/ChatWebSocketController.java:32-32
Timestamp: 2025-08-01T04:15:35.434Z
Learning: 사용자 gkdudans는 디버깅 코드(System.out.println)를 제거하고, 중요한 로그만 log.info나 log.warn을 사용하여 적절한 로깅 프레임워크로 남기는 것을 선호한다.
Applied to files:
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java
🔇 Additional comments (4)
src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java (4)
21-27: 지역 기반 검색 구현이 적절합니다.도시와 구역을 모두 일치시켜 정확한 지역 필터링을 제공하는 구현이 좋습니다.
37-45: 사용자 맞춤 추천 1단계 구현이 우수합니다.관심사와 지역을 모두 고려하여 개인화된 추천을 제공하는 로직이 잘 구현되었습니다. 인기순과 최신순의 2차 정렬도 적절합니다.
48-53: 관심사 기반 추천 로직이 적절합니다.지역 매칭이 없을 때 관심사만으로 추천하는 대체 전략이 잘 구현되었습니다.
56-80: FULLTEXT 인덱스 미확인: 수동 검증 필요다음 사항을 검토해주세요:
- schema.sql 또는 Liquibase/Flyway 마이그레이션 파일에서
club(name, description)에 대한 FULLTEXT 인덱스 정의가 보이지 않습니다.- 인덱스가 없다면 ▼
• 직접 확인 후 추가 migration 파일 작성
• 기존 마이그레이션에 인덱스 정의 반영
#️⃣ Issue Number
closed #28
closed #29
closed #30
closed #31
closed #80
📝 요약(Summary)
🛠️ PR 유형
어떤 변경 사항이 있나요?
📸스크린샷 (선택)
💬 공유사항 to 리뷰어
✅ PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.
Summary by CodeRabbit
신규 기능
버그 수정
기타