Skip to content

Conversation

@Gyuhyeok99
Copy link
Contributor

관련 이슈

작업 내용

매칭된 멘티/멘토 정보 조회 기능 구현합니다.
스크린샷 2025-08-25 오후 10 42 02

브루노 pr

특이 사항

리뷰 요구사항 (선택)

@coderabbitai
Copy link

coderabbitai bot commented Aug 25, 2025

Walkthrough

    1. 컨트롤러 추가.
    • GET /mentee/mentorings/matched-mentors 엔드포인트가 추가되었고, MENTEE 권한으로 보호되며 Pageable 기본 정렬은 confirmedAt DESC, id DESC입니다.
    1. DTO 도입.
    • MatchedMentorResponse 레코드가 추가되어 멘토/사용자/대학교/채널/지원여부/roomId를 맵핑하는 of(...) 정적 팩토리를 제공합니다.
    1. 리포지토리 확장.
    • 승인 상태 기준으로 멘티 ID와 멘토 ID 목록 필터링, 그리고 멘티 ID 기준 페이지 조회용 @query 메서드 두 개가 추가되었습니다.
    1. 서비스 로직 추가.
    • MentoringQueryService에 getMatchedMentors가 추가되어 배치 쿼리로 관련 데이터(사이트유저, 대학교, 지원여부, roomId)를 집계합니다.
    1. 테스트 보강.
    • 대학교/채널 픽스처 도입, 매칭 멘토 조회와 채팅방/채널/페이징 검증 테스트가 추가되었습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wibaek
  • nayonsoso
  • lsy1307

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.
    • 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.
  • 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 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/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

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: 3

🧹 Nitpick comments (8)
src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java (2)

20-31: 자주 쓰이는 조건 조합에 대한 인덱스 제안.

  1. 본 PR에서 자주 조회되는 조건이 menteeId, mentorId, verifyStatus 조합입니다.
  2. Mentoring(menteeId, verifyStatus), Mentoring(menteeId, mentorId), 필요 시 Mentoring(mentorId, verifyStatus) 복합 인덱스를 고려해 주세요.
  3. 실제 트래픽 및 실행 계획을 확인해 우선순위를 조정하면 좋습니다.

실 DB에 인덱스가 존재하는지 확인해 주세요. 필요 시 이슈로 분리해도 좋습니다.


32-38: 메서드 이름과 실제 동작 불일치 개선 제안
메서드명과 verifyStatus 파라미터 활용 범위가 일치하지 않아 혼동을 줄 수 있습니다.
검증 결과, 현재 호출부(MentoringQueryService.java:78)에서는 항상 VerifyStatus.APPROVED만 전달하고 있어 사실상 ‘APPROVED’ 고정 쿼리로 동작하고 있습니다.
아래 두 가지 중 하나를 선택하여 일관성을 맞추시길 제안드립니다.

  1. 메서드명 변경
    • 메서드명을 findMentoringsByMenteeIdAndMentorIdsAndVerifyStatus 등으로 변경하여 verifyStatus 파라미터 의도를 명확히 합니다.
  2. verifyStatus 고정
    • 메서드 시그니처에서 verifyStatus 파라미터를 제거하고, @Query에서 VerifyStatus.APPROVED를 직접 사용하여 ‘Approved’ 전용 메서드로 유지합니다.
src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java (1)

28-36: 선택적 리팩터링 제안: 메소드명·파라미터 정리

확인 결과, MentoringQueryService에서 findApprovedMentorsByMenteeId를 호출할 때 오직 VerifyStatus.APPROVED만 전달되고 있습니다.
팀의 목적에 따라 아래 두 가지 옵션 중 하나를 선택해 반영해 주세요:

  1. 범용 조회 지원이 필요하다면
    1.1) 메소드명을 findMentorsByMenteeIdAndVerifyStatus로 변경합니다.
    1.2) 기존의 verifyStatus 파라미터는 그대로 유지하여 다양한 상태 조회가 가능하도록 합니다.

  2. ‘APPROVED’ 전용 조회만 사용할 계획이라면
    2.1) verifyStatus 파라미터를 제거합니다.
    2.2) JPQL 쿼리에서 AND mt.verifyStatus = 'APPROVED'처럼 상수로 고정해 코드가 더 간결해지도록 합니다.

위 옵션 중 팀의 사용 시나리오와 유지보수 편의성을 고려해 결정해 주세요.

src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java (1)

42-55: 페이지 크기 기본값 명시로 API 일관성 강화.

  1. 동일 컨트롤러의 getMentorings는 @PageableDefault(size = 3)을 사용하지만, 본 엔드포인트는 크기 미지정입니다.
  2. 클라이언트 경험을 위해 기본 size를 명시(예: 10)하는 것을 권장합니다.

아래와 같이 size를 지정해 보세요.

-            @PageableDefault
+            @PageableDefault(size = 10)
             @SortDefaults({
                 @SortDefault(sort = "menteeCount", direction = Sort.Direction.DESC),
                 @SortDefault(sort = "id", direction = Sort.Direction.ASC)
             })
src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java (1)

11-27: DTO 필드 구성 일관성 및 확장 포인트 제안.

  1. MentorDetailResponse와 구성 필드가 유사하므로 필드/네이밍 일관성을 유지한 점이 좋습니다.
  2. isApplied/hasBadge 같은 불리언 접두사는 JSON에서도 의도가 명확합니다.
  3. 추후 API 문서(Swagger) 노출을 위해 @Schema(description = "...")를 각 필드에 부여하면 프런트 협업이 수월해집니다.
src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java (3)

312-316: [5] 다음 페이지 존재 검증을 강화해 의미를 더 또렷하게 합시다.

  • nextPageNumber만 검증하면 페이지 크기/정렬 변경 시 실패 원인이 모호할 수 있습니다.
  • 현재 데이터(2건)와 pageSize=1을 전제로, “이번 페이지의 콘텐츠가 정확히 1건”임을 함께 확인해 주세요.
             // then
+            assertThat(response.content()).hasSize(1);
             assertThat(response.nextPageNumber()).isEqualTo(2);

321-325: [6] 다음 페이지가 없을 때의 시맨틱 검증을 보강합시다.

  • “더 이상 페이지가 없다”는 의미가 데이터 상으로도 드러나면 좋습니다.
  • 총 2건, pageSize=10인 상황이므로 콘텐츠가 2건임을 함께 확인해 주세요.
  • 상단 코멘트(항목 [2])를 채택했다면 상수는 SliceResponse.NO_NEXT_PAGE_NUMBER를 직접 참조해도 됩니다.
             // then
+            assertThat(response.content()).hasSize(2);
             assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER);

281-307: 테스트 검증 강화 제안

아래 세 가지 변경 사항을 적용하면 누락·중복 이슈를 더 명확하게 잡아내고, “지원 여부” 회귀를 방지할 수 있습니다:

  1. 사전 조건 검증 추가
     • assertThat(response.content()).hasSize(2); 로 반환 개수가 정확히 2건인지 명시적으로 확인합니다.
  2. 반환 키셋 검증 추가
     • assertThat(matchMentorMap.keySet())    .containsExactlyInAnyOrder(mentor1.getId(), mentor2.getId());
     로 기대 멘토 ID 집합만 포함됐는지 검증합니다.
  3. isApplied 필드 검증 추가
     • assertThat(mentor1Response.isApplied()).isTrue()
     • assertThat(mentor2Response.isApplied()).isTrue()
     를 통해 각 멘토의 지원 여부(boolean isApplied)를 검증합니다.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f8c493 and e4fb424.

📒 Files selected for processing (6)
  • src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java (2 hunks)
  • src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java (1 hunks)
  • src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java (2 hunks)
  • src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java (2 hunks)
  • src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java (1)
src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java (1)
  • MentorDetailResponse (8-40)
⏰ 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 (9)
src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java (1)

42-55: 정렬 필드 유효성 점검 알림.

  1. 정렬 필드 menteeCount, id가 Mentor 엔티티에 정확히 매핑되는지 재확인해 주세요.
  2. DB 컬럼/엔티티 필드명이 다르면 런타임 오류가 발생합니다.

테스트에 정렬 시나리오가 포함되어 있는지 확인해 주세요. 없다면 간단한 통합 테스트 추가를 권장합니다.

src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java (3)

43-51: 배치 조회 방식 설계, 방향 좋습니다.

  1. @transactional(readOnly = true)로 조회 트랜잭션을 명확히 했습니다.
  2. SliceResponse.of를 통해 페이지 정보를 유지하며 DTO 리스트를 반환합니다.

53-55: 조회 전용 리포지토리 호출 흐름 적절.

  1. APPROVED만 필터링하는 filterMatchedMentors 분리는 가독성과 재사용성 측면에서 좋습니다.

57-74: 리뷰 코멘트 불필요: 배치 쿼리 구현이 이미 Null 안전을 보장합니다

  1. 언박싱 안전 보장
     MentorBatchQueryRepository의 getMentorIdToIsAppliedmentors.stream()으로 모든 멘토 ID에 대한 Boolean 값을 매핑하고, primitive boolean이 자동 박싱되어 Map에 항상 non-null 값(true/false)으로 저장됩니다. 따라서 언박싱 시 NPE 발생 가능성이 없습니다.

  2. SiteUser/University 누락 방어
    getMentorIdToSiteUserMapgetMentorIdToUniversityMap는 조회 결과가 없을 경우 즉시 CustomException(DATA_INTEGRITY_VIOLATION)을 던져 데이터 불일치를 방어합니다. 서비스 레이어에서 추가적인 null 체크나 예외 처리는 중복이며 불필요합니다.

  3. 추가 변경 불필요
     현재 구현은 정상 동작하며, 서비스 메서드에 제안된 Objects.requireNonNull이나 getOrDefault 로직 삽입은 오히려 코드 중복을 유발합니다.

Likely an incorrect or invalid review comment.

src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java (2)

14-16: roomId의 조건부 직렬화 적절.

  1. 채팅방 미생성 상태에서 roomId를 생략하도록 한 @JsonInclude(NON_NULL)가 요구사항과 잘 맞습니다.
  2. 상위 서비스에서 null이 Map에 값으로 들어가지 않도록 주의하는 선행 조치가 필요합니다(서비스 파일 코멘트 적용 시 해결).

roomId가 null일 때 JSON 필드가 누락되는지(포함되지 않는지) 스냅샷 테스트로 확인해 주세요.


29-45: of(...) 매퍼의 Null 안전성 검토.

  1. university 또는 mentorUser가 null이면 NPE가 발생합니다.
  2. 배치 쿼리 단계에서 누락 가능성을 제거하거나, DTO 생성 직전에 명시적 처리(예외/로그/대체값)를 검토해 주세요.

배치 리포지토리가 모든 Mentor에 대해 SiteUser/University를 항상 반환하는지 테스트로 보장해 주세요.

src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java (3)

83-87: [1] 대학 픽스처로 멘토-대학 연동 초기화가 정확합니다.

  • 대학을 먼저 생성하고 mentorFixture에 universityId를 주입하는 흐름이 명확합니다.
  • 현재 구조로 이후 검증(대학명, 국가명)까지 일관되게 assert 가능한 점이 좋습니다.

269-276: [3] 중첩 클래스의 테스트 데이터 시드가 간결하고 재사용성이 좋습니다.

  • 승인된 멘토링 2건과 각 채팅방을 1:1로 준비해 시나리오를 선명하게 만듭니다.
  • 상위 @beforeeach 이후에 이 세팅이 얹히는 구조도 자연스럽습니다.

261-266: SliceResponse에 NO_NEXT_PAGE_NUMBER 상수가 없어 제안한 참조가 불가능합니다.

  1. 상수 미존재 확인
     SliceResponse 클래스에 NO_NEXT_PAGE_NUMBER 상수가 정의되어 있지 않으므로
    SliceResponse.NO_NEXT_PAGE_NUMBER를 참조하면 컴파일 오류가 발생합니다.

  2. 수정 제안
     • 프로덕션 코드에 해당 상수를 먼저 추가하거나
     • 테스트에서는 여전히 -1 리터럴을 쓰되, response.hasNext() 결과를 함께 검증하도록 바꿔주세요.

Likely an incorrect or invalid review comment.

Comment on lines +32 to +38
@Query("""
SELECT m FROM Mentoring m
WHERE m.menteeId = :menteeId
AND m.mentorId IN :mentorIds
AND m.verifyStatus = :verifyStatus
""")
List<Mentoring> findApprovedMentoringsByMenteeIdAndMentorIds(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, @Param("mentorIds") List<Long> mentorIds);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 mentorIds 전달 시 IN 절 예외 가능성 방어 필요.

  1. JPQL의 IN (:mentorIds)는 빈 리스트 전달 시 데이터베이스/프로바이더에 따라 예외가 발생할 수 있습니다.
  2. 상위 서비스에서 mentors가 비어 있으면 조기 반환하도록 가드 코드를 추가하는 것이 안전합니다.
  3. 본 파일에서 방어하기 어렵다면, 서비스 메서드(getMentorIdToRoomIdMap) 측에 조기 반환을 추가하는 패치를 제안합니다(아래 서비스 파일 코멘트 참고).
🤖 Prompt for AI Agents
In
src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java
around lines 32-38, the JPQL IN(:mentorIds) can throw when mentorIds is empty;
instead of changing the repository, update the caller service method
(getMentorIdToRoomIdMap) to check if the mentorIds (or mentors list) is empty
and return an empty result immediately, skipping the repository call; ensure the
service performs a null/empty check on the list and returns
Collections.emptyMap() (or equivalent) so no empty IN clause is ever passed to
the repository.

Comment on lines 28 to 36
@Query("""
SELECT m FROM Mentor m
WHERE m.id IN (
SELECT mt.mentorId FROM Mentoring mt
WHERE mt.menteeId = :menteeId
AND mt.verifyStatus = :verifyStatus
)
""")
Slice<Mentor> findApprovedMentorsByMenteeId(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

IN 서브쿼리 대신 EXISTS로 변경 및 중복 방지 제안.

  1. 현재 쿼리는 IN (SELECT ...) 형태라 옵티마이저에 따라 비효율/중복 가능성이 있습니다.
  2. EXISTS로 변경하면 중복 Mentor를 원천 방지하고, 조건 만족 여부만 확인하므로 의도가 명확해집니다.
  3. Pageable과 함께 사용할 때도 DISTINCT 없이 안전합니다.

아래와 같이 교체를 제안드립니다.

-    @Query("""
-           SELECT m FROM Mentor m
-           WHERE m.id IN (
-               SELECT mt.mentorId FROM Mentoring mt
-               WHERE mt.menteeId = :menteeId
-               AND mt.verifyStatus = :verifyStatus
-           )
-           """)
-    Slice<Mentor> findApprovedMentorsByMenteeId(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
+    @Query("""
+           SELECT m FROM Mentor m
+           WHERE EXISTS (
+               SELECT 1 FROM Mentoring mt
+               WHERE mt.mentorId = m.id
+                 AND mt.menteeId = :menteeId
+                 AND mt.verifyStatus = :verifyStatus
+           )
+           """)
+    Slice<Mentor> findApprovedMentorsByMenteeId(@Param("menteeId") long menteeId,
+                                                @Param("verifyStatus") VerifyStatus verifyStatus,
+                                                Pageable pageable);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Query("""
SELECT m FROM Mentor m
WHERE m.id IN (
SELECT mt.mentorId FROM Mentoring mt
WHERE mt.menteeId = :menteeId
AND mt.verifyStatus = :verifyStatus
)
""")
Slice<Mentor> findApprovedMentorsByMenteeId(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
@Query("""
SELECT m FROM Mentor m
WHERE EXISTS (
SELECT 1 FROM Mentoring mt
WHERE mt.mentorId = m.id
AND mt.menteeId = :menteeId
AND mt.verifyStatus = :verifyStatus
)
""")
Slice<Mentor> findApprovedMentorsByMenteeId(@Param("menteeId") long menteeId,
@Param("verifyStatus") VerifyStatus verifyStatus,
Pageable pageable);
🤖 Prompt for AI Agents
In
src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java
around lines 28-36, replace the IN (SELECT ...) JPQL with an EXISTS correlated
subquery to avoid potential duplicates and optimizer inefficiencies: rewrite the
query to SELECT m FROM Mentor m WHERE EXISTS (SELECT 1 FROM Mentoring mt WHERE
mt.mentorId = m.id AND mt.menteeId = :menteeId AND mt.verifyStatus =
:verifyStatus); keep the method signature and parameters unchanged so Pageable
continues to work without needing DISTINCT.

private final MentorBatchQueryRepository mentorBatchQueryRepository;

@Transactional(readOnly = true)
public SliceResponse<MatchedMentorResponse> getMatchedMentors(long siteUserId, Pageable pageable) {
Copy link
Member

Choose a reason for hiding this comment

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

filterMatchedMentors 메서드의 filterMatchedMentors 에서 멘티와 매칭된 멘토 목록을 조회하기 위해 mentoring 테이블 접근 한 번, 멘토 목록을 기반으로 채팅방 ID를 가져오는 데 mentoring 테이블 접근 한 번 총 두 번의 테이블 접근이 발생하는 것 같습니다. 아예 mentoring 테이블을 처음에 가져오고, 관련 멘토 정보를 가져오는 방법도 좋을 것 같습니다.

예를 들어

  1. 멘티 ID를 통해 멘토링 전부 조회
  2. 해당 멘토링에 속한 멘토 조회
  3. 이하 동(멘토링 ID에 대해 채팅방 조회 후 응답 만들기)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그게 좋겠네요! 바꿨습니다! 543e4ae

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: 2

♻️ Duplicate comments (2)
src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java (1)

78-93: Collectors.toMap에서 null/중복 키로 인한 예외 가능성을 제거해 주세요.

    1. mentors/approvedMentorings/mentoringIds가 비면 즉시 빈 Map을 반환해야 IN 절/빈 결과 접근에서 안전합니다.
    1. mentoringIdToRoomId, mentorIdToRoomId 구성 시 중복 키가 생기면 IllegalStateException이 납니다. 머지 함수로 방지하세요.
    1. 아직 ChatRoom 미생성인 승인 멘토링(=roomId null) 엔트리는 필터링해야 NPE를 예방합니다.

안전 구현 예시:

-    private Map<Long, Long> getMentorIdToRoomIdMap(List<Mentor> mentors, long menteeUserId) {
-        List<Long> mentorIds = mentors.stream().map(Mentor::getId).toList();
-        List<Mentoring> approvedMentorings = mentoringRepository.findApprovedMentoringsByMenteeIdAndMentorIds(menteeUserId, VerifyStatus.APPROVED, mentorIds);
-
-        List<Long> mentoringIds = approvedMentorings.stream().map(Mentoring::getId).toList();
-        List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
-
-        Map<Long, Long> mentoringIdToRoomId = chatRooms.stream()
-                .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId));
-
-        return approvedMentorings.stream()
-                .collect(Collectors.toMap(
-                        Mentoring::getMentorId,
-                        mentoring -> mentoringIdToRoomId.get(mentoring.getId())
-                ));
-    }
+    private Map<Long, Long> getMentorIdToRoomIdMap(List<Mentor> mentors, long menteeUserId) {
+        if (mentors == null || mentors.isEmpty()) {
+            return java.util.Collections.emptyMap();
+        }
+        List<Long> mentorIds = mentors.stream().map(Mentor::getId).toList();
+        if (mentorIds.isEmpty()) {
+            return java.util.Collections.emptyMap();
+        }
+        List<Mentoring> approvedMentorings =
+                mentoringRepository.findApprovedMentoringsByMenteeIdAndMentorIds(menteeUserId, VerifyStatus.APPROVED, mentorIds);
+        if (approvedMentorings.isEmpty()) {
+            return java.util.Collections.emptyMap();
+        }
+
+        List<Long> mentoringIds = approvedMentorings.stream().map(Mentoring::getId).toList();
+        if (mentoringIds.isEmpty()) {
+            return java.util.Collections.emptyMap();
+        }
+        List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
+
+        Map<Long, Long> mentoringIdToRoomId = chatRooms.stream()
+                .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId, (a, b) -> a));
+
+        java.util.Map<Long, Long> result = new java.util.HashMap<>();
+        for (Mentoring mentoring : approvedMentorings) {
+            Long roomId = mentoringIdToRoomId.get(mentoring.getId());
+            if (roomId != null) {
+                // 동일 멘토에 다수 방이 있을 때, 첫 값을 유지(비즈니스 규칙에 따라 변경 가능).
+                result.putIfAbsent(mentoring.getMentorId(), roomId);
+            }
+        }
+        return result;
+    }

추가 요청

    1. “승인됐지만 채팅방 미생성” 케이스를 커버하는 테스트를 추가해 주세요.
    1. mentors/mentorIds/mentoringIds가 빈 경우 예외 없이 빈 Map을 얻는지 테스트로 검증해 주세요.
src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java (1)

32-39: IN (:mentorIds)에 빈 리스트 전달 시 예외 가능성을 상위 서비스에서 방어해 주세요.

    1. JPA 구현/DB 설정에 따라 IN () 빈 컬렉션은 예외를 유발할 수 있습니다.
    1. 상위 서비스(getMentorIdToRoomIdMap)에서 mentorIds 비어 있으면 조기 반환하도록 가드하는 것이 가장 안전합니다.
    1. 본 PR의 서비스 코멘트로 동일 사안을 제안드렸으며, 해당 방어 로직을 적용하면 본 리포지토리 변경은 불필요합니다.
🧹 Nitpick comments (3)
src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java (2)

47-57: 페이지네이션 기준 불일치(멘토링 단위 → 멘토 단위 중복 제거)로 인한 응답/메타데이터 불일치를 점검해 주세요.

    1. 현재는 Mentoring Slice를 기반으로 mentorIds를 distinct 처리하여 멘토 단위로 응답을 생성하므로, content 사이즈가 요청 페이지 크기보다 작아지고 hasNext 등 메타가 Mentoring 단위를 따르는 불일치가 생길 수 있습니다.
    • 제안 A: “멘토 단위 페이지네이션”으로 전환하는 전용 리포지토리 쿼리를 도입하세요(멘토별 최신 confirmedAt/id 기준).
      • 예시: mentorId를 group by로 뽑고 max(confirmedAt), max(id)로 정렬한 Distinct Mentor 페이지.
    • 제안 B: 현재 방식 유지 시, 중복 제거로 인한 사이즈 감소와 메타 불일치 가능성을 API 스펙/문서에 명시해 주세요.
    1. findAllById는 반환 순서를 보장하지 않을 수 있어 정렬 안정성이 깨질 수 있습니다. mentorIds 순서로 재정렬 후 build 함수로 넘겨주세요(아래 패치).

정렬 안정성 보장을 위한 패치 예시:

-        List<Mentor> mentors = mentorRepository.findAllById(mentorIds);
-
-        List<MatchedMentorResponse> content = buildMatchedMentorsWithBatchQuery(mentors, siteUserId);
+        List<Mentor> mentors = mentorRepository.findAllById(mentorIds);
+        java.util.Map<Long, Mentor> mentorById = mentors.stream()
+                .collect(java.util.stream.Collectors.toMap(Mentor::getId, java.util.function.Function.identity()));
+        java.util.List<Mentor> orderedMentors = mentorIds.stream()
+                .map(mentorById::get)
+                .filter(java.util.Objects::nonNull)
+                .toList();
+
+        List<MatchedMentorResponse> content = buildMatchedMentorsWithBatchQuery(orderedMentors, siteUserId);

추가 import가 필요합니다(파일 상단):

import java.util.Objects;

121-130: 빈 리스트 가드 및 중복 키 머지 함수로 toMap 안정성을 높여 주세요.

    1. mentorings가 빈 경우 바로 빈 Map을 반환하면 리포지토리 호출을 줄일 수 있습니다.
    1. 동일 mentoringId에 다수 ChatRoom이 존재할 가능성(이력/재생성 등)에 대비해 머지 함수를 지정하세요.

적용 패치 예시:

     private Map<Long, Long> mapMentoringIdToChatRoomIdWithBatchQuery(List<Mentoring> mentorings) {
-        List<Long> mentoringIds = mentorings.stream()
+        if (mentorings == null || mentorings.isEmpty()) {
+            return java.util.Collections.emptyMap();
+        }
+        List<Long> mentoringIds = mentorings.stream()
                 .map(Mentoring::getId)
                 .distinct()
                 .toList();
         List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
-        return chatRooms.stream()
-                .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId));
+        return chatRooms.stream()
+                .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId, (a, b) -> a));
     }
src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java (1)

32-41: 쿼리 접근 패턴에 맞춘 인덱스 제안으로 조회 성능을 안정화해 주세요.

    1. menteeId + verifyStatus 조건과 mentorId IN 필터가 빈번합니다. (menteeId, verifyStatus, confirmedAt DESC, id DESC) 복합 인덱스를 제안드립니다.
    1. 멘토 전용 조회(findAllByMentorId)도 빈번하므로 (mentorId, id DESC) 보조 인덱스를 고려해 주세요.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e4fb424 and 543e4ae.

📒 Files selected for processing (3)
  • src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java (2 hunks)
  • src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java (1 hunks)
  • src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.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

Comment on lines +40 to +41
@Query("SELECT m FROM Mentoring m WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus")
Slice<Mentoring> findApprovedMentoringsByMenteeId(long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

@param 누락으로 인한 런타임 바인딩 실패 가능성을 제거해 주세요.

    1. 쿼리에서 :menteeId를 사용하지만, 메서드 시그니처의 menteeId 파라미터에 @param("menteeId")가 없습니다.
    1. 컴파일 옵션(-parameters) 유무에 따라 런타임 BindException이 발생할 수 있으므로 명시 주입을 권장합니다.

수정 예시:

-    Slice<Mentoring> findApprovedMentoringsByMenteeId(long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
+    Slice<Mentoring> findApprovedMentoringsByMenteeId(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Query("SELECT m FROM Mentoring m WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus")
Slice<Mentoring> findApprovedMentoringsByMenteeId(long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
@Query("SELECT m FROM Mentoring m WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus")
Slice<Mentoring> findApprovedMentoringsByMenteeId(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
🤖 Prompt for AI Agents
In
src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java
around lines 40-41, the JPQL uses :menteeId but the method parameter lacks
@Param("menteeId"), which can cause runtime BindException when -parameters is
not enabled; add @Param("menteeId") to the menteeId parameter (and keep the
existing @Param("verifyStatus") for verifyStatus) so both named parameters are
explicitly bound in the repository method signature.

Comment on lines +59 to +76
private List<MatchedMentorResponse> buildMatchedMentorsWithBatchQuery(List<Mentor> mentors, long currentUserId) {
Map<Long, SiteUser> mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors);
Map<Long, University> mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors);
Map<Long, Boolean> mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId);

Map<Long, Long> mentorIdToRoomId = getMentorIdToRoomIdMap(mentors, currentUserId);

List<MatchedMentorResponse> matchedMentors = new ArrayList<>();
for (Mentor mentor : mentors) {
SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId());
University university = mentorIdToUniversity.get(mentor.getId());
boolean isApplied = mentorIdToIsApplied.get(mentor.getId());
Long roomId = mentorIdToRoomId.get(mentor.getId());
MatchedMentorResponse response = MatchedMentorResponse.of(mentor, mentorUser, university, isApplied, roomId);
matchedMentors.add(response);
}
return matchedMentors;
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

불리언 언박싱으로 인한 NPE 위험 제거 및 널 안전성 강화가 필요합니다.

    1. isApplied를 Boolean → boolean 언박싱할 때 키 미존재 시 NPE가 납니다.
    • 해결: Boolean.TRUE.equals(...)로 안전 접근하세요.
    1. mentors가 비었을 때는 하위 배치 쿼리를 건너뛰고 조기 반환하는 편이 안전합니다.
    • 해결: 초반 가드 추가.
    1. siteUser/university가 누락될 수 있다면, of(...)가 널 허용인지 확인해 주세요. 널 비허용이라면 기본값/검증 추가가 필요합니다.

적용 패치 예시:

-    private List<MatchedMentorResponse> buildMatchedMentorsWithBatchQuery(List<Mentor> mentors, long currentUserId) {
+    private List<MatchedMentorResponse> buildMatchedMentorsWithBatchQuery(List<Mentor> mentors, long currentUserId) {
+        if (mentors == null || mentors.isEmpty()) {
+            return java.util.Collections.emptyList();
+        }
         Map<Long, SiteUser> mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors);
         Map<Long, University> mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors);
         Map<Long, Boolean> mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId);
 
         Map<Long, Long> mentorIdToRoomId = getMentorIdToRoomIdMap(mentors, currentUserId);
 
         List<MatchedMentorResponse> matchedMentors = new ArrayList<>();
         for (Mentor mentor : mentors) {
             SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId());
             University university = mentorIdToUniversity.get(mentor.getId());
-            boolean isApplied = mentorIdToIsApplied.get(mentor.getId());
+            boolean isApplied = java.lang.Boolean.TRUE.equals(mentorIdToIsApplied.get(mentor.getId()));
             Long roomId = mentorIdToRoomId.get(mentor.getId());
             MatchedMentorResponse response = MatchedMentorResponse.of(mentor, mentorUser, university, isApplied, roomId);
             matchedMentors.add(response);
         }
         return matchedMentors;
     }

추가 요청

    1. Line [70]의 언박싱 NPE를 재현하는 테스트를 하나 추가해 주세요.
    1. mentors가 빈 경우에도 빈 리스트를 반환하는지 확인하는 테스트를 추가해 주세요.

🏁 Script executed:

#!/bin/bash
# Locate MentorBatchQueryRepository interface and verify its methods
rg -n 'interface MentorBatchQueryRepository' -C3 -g '*.java'
rg -n 'class MentorBatchQueryRepository' -C3 -g '*.java'

# Inspect implementations of batch query methods
rg -n 'getMentorIdToSiteUserMap' -C3 -g '*.java'
rg -n 'getMentorIdToUniversityMap' -C3 -g '*.java'
rg -n 'getMentorIdToIsApplied' -C3 -g '*.java'

# Examine MatchedMentorResponse.of(...) signature to check null handling
rg -n 'class MatchedMentorResponse' -C5 -g '*.java'
rg -n 'of\s*\(' -C3 -g 'src/main/java/com/example/solidconnection/mentor/service/MatchedMentorResponse.java'

Length of output: 15164


🏁 Script executed:

#!/bin/bash
# 1) MatchedMentorResponse.java 파일 위치 찾기
find src -type f -name 'MatchedMentorResponse.java'

# 2) of(...) 메서드 선언부와 널 처리 여부 확인
file=$(find src -type f -name 'MatchedMentorResponse.java')
rg -n 'of\s*\(' -C5 "$file"

Length of output: 825


매칭된 멘토 리스트 처리 로직 null 안정성 강화 요청

  1. Boolean 언박싱 NPE 방지
     - mentorIdToIsApplied.get(id)null을 반환할 경우 기본 언박싱(boolean isApplied = …) 시 NPE가 발생합니다.
     - boolean isApplied = Boolean.TRUE.equals(mentorIdToIsApplied.get(mentor.getId())); 와 같이 안전하게 변환해 주세요.

  2. 빈 멘토 리스트 조기 반환 가드 추가
     - mentorsnull이거나 비어있는 경우 하위 배치 쿼리를 건너뛰고 즉시 빈 리스트를 반환하도록 방어 로직을 추가해 주세요.

  3. SiteUser/University null 검증 또는 기본값 제공
     - MatchedMentorResponse.of(mentor, mentorUser, university, …) 내부에서 mentorUser.getNickname() 호출 시 NPE가 발생할 수 있습니다.
     - mentorUser 또는 universitynull인지 확인하고, 필요 시 기본값을 주입하거나 검증 로직을 추가해 주세요.

추가 테스트 작성 요청
 - isAppliednull 맵값을 받을 때도 NPE 없이 false로 처리되는지 검증하는 테스트
 - mentors가 빈 리스트 또는 null일 때 빈 결과 리스트를 반환하는지 검증하는 테스트

🤖 Prompt for AI Agents
In
src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java
around lines 59-76, harden buildMatchedMentorsWithBatchQuery: add an early
return if mentors is null or empty to skip batch queries and return
Collections.emptyList(); convert the isApplied mapping lookup to a null-safe
boolean via Boolean.TRUE.equals(mentorIdToIsApplied.get(mentor.getId())); and
null-check mentorUser and university before using them (either inject simple
defaults or validate and pass safe values into MatchedMentorResponse.of to avoid
downstream NPEs, e.g., use a fallback SiteUser/University or pass null-safe
fields). Also add unit tests that verify: (a) null map values for isApplied are
treated as false without NPE, and (b) null or empty mentors input returns an
empty list.

@Gyuhyeok99 Gyuhyeok99 merged commit 91feb3d into solid-connection:develop Aug 30, 2025
2 checks passed
@Gyuhyeok99 Gyuhyeok99 deleted the feat/478-matched-mentoring-info branch September 25, 2025 09:46
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.

feat: 매칭된 멘티/멘토 정보 조회 기능 구현

2 participants