Skip to content

[refactor] 기록장 조회 api 수정#66

Merged
buzz0331 merged 32 commits intodevelopfrom
refactor/#63-get-record-vote
Jul 13, 2025
Merged

[refactor] 기록장 조회 api 수정#66
buzz0331 merged 32 commits intodevelopfrom
refactor/#63-get-record-vote

Conversation

@buzz0331
Copy link
Contributor

@buzz0331 buzz0331 commented Jul 12, 2025

#️⃣ 연관된 이슈

closes #63

📝 작업 내용

#52 저번 기록장 조회 api에서 잘못된 로직을 수정했습니다. 바뀐 부분은 다음과 같습니다.

  1. 정렬 로직 수정 (기존에 DateUtil을 통해 파싱된 문자열을 기준으로 정렬했기 때문에 코드 논리의 오류가 있어 파싱되기 전에 정렬하도록 수정하였습니다.)
  2. 페이징 처리 개선 (기존 전체 데이터를 조회하여 서브 리스트로 잘라내는 방식은 성능상 이슈가 매우 클 것으로 우려되어 DB에서 offset 기반으로 페이징 처리하도록 수정하였습니다. 이 부분도 추후에 성준님이 말씀하신 커서 기반의 페이징 처리로 수정하면 좋을 것 같습니다.)
  3. 유효성 검증 수정 (기존에는 총평 기록 조회 요청을 위해 pageStart = null, pageEnd = null로 받았는데, 이렇게 할 경우 0 ~ 책 전체 페이지 에 대한 요청을 받을 수 없다는 문제가 있어, isOverview라는 파라미터를 하나 더 받는 것으로 결정하였습니다.)

웬만한 유효성 검증은 다 한 것 같은데 빠진 부분이 있을 수 있으니 한번씩 확인 부탁드릴게요!

📸 스크린샷

💬 리뷰 요구사항

현재 Post 테이블로의 조회가 불가피하기 때문에 JPA 자체적으로 자동으로 Feed, Record, Vote와 left join 3개 + Record, Vote의 필드를 기준으로 정렬 또는 필터링해야 하기 때문에 명시적인 left join 2개로 총 5개의 join이 이루어지고 있어서 성능이 매우 우려되는 상황입니다. 따라서, 이를 추후에 리팩토링 시에 Post 테이블을 공통 테이블로 모든 Feed Record Vote 데이터들을 하나의 테이블에 넣고 type으로 관리하는 방법을 사용하여 join을 최소화 시키는 전략을 사용하려고 합니다! 노션에 문제상황 정리해두었으니 참고 부탁드릴게요~!

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능

    • 기록(Record) 및 투표(Vote)에 좋아요 수(likeCount)와 댓글 수(commentCount) 필드가 추가되어, 해당 정보를 확인할 수 있습니다.
    • 기록 목록 조회 시 정렬 및 필터링(최신순, 좋아요순, 댓글순, 그룹/내 기록) 기능이 지원됩니다.
    • 기록 목록 API에 isOverview 파라미터가 추가되어, 개요(Overview) 조회가 가능합니다.
    • 방 ID로 예약 정보를 조회하는 기능이 추가되었습니다.
  • 버그 수정

    • 기록 및 투표 조회 시 페이징, 정렬, 필터링 관련 동작이 개선되었습니다.
  • 리팩터링/구조 개선

    • 기록 목록 조회 시 페이징 처리 및 반환 구조가 Page 기반으로 변경되어, 더 효율적인 데이터 처리가 가능합니다.
    • 불필요한 DTO 및 메서드가 정리되고, 파라미터 전달 방식이 명확해졌습니다.
    • 기록 조회 쿼리가 다형성, 페이징, 동적 정렬을 지원하도록 개선되었습니다.
    • 날짜 표시 방식이 정적 메서드로 변경되어 호출이 간편해졌습니다.
  • 테스트

    • 기록 및 투표 관련 테스트가 페이징, 정렬, 필터링 등 다양한 케이스를 포괄하도록 확장되었습니다.
    • 입력값 검증 및 예외 처리에 대한 테스트가 추가되었습니다.

buzz0331 added 25 commits July 11, 2025 16:34
- 유효성 검증 추가 (isOverview 관련)
- 정렬 조건 확인
- Pageable 객체 생성
- isLiked와 voteItems 직접 조회해서 파싱
@coderabbitai
Copy link

coderabbitai bot commented Jul 12, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

기록장(Record) 조회 API의 전체 로직이 대대적으로 리팩토링되었습니다. 정렬, 페이징, 타입 필터링 등 모든 쿼리 파라미터가 명시적 파라미터로 변경되고, Querydsl 기반 동적 쿼리 및 Spring Data의 페이징/정렬 기능이 도입되었습니다. 관련 도메인, 엔티티, 매퍼, 컨트롤러, 테스트 코드가 모두 이에 맞게 수정되었습니다.

Changes

파일/경로 그룹 변경 요약
.../BookCommandPersistenceAdapter.java, .../BookCommandPort.java Room ID로 Book을 조회하는 메서드 추가
.../DateUtil.java @Component 제거, 메서드 static화 및 이름 변경
.../feed/adapter/out/jpa/FeedJpaEntity.java, .../post/adapter/out/jpa/PostJpaEntity.java, .../record/adapter/out/jpa/RecordJpaEntity.java, .../vote/adapter/out/jpa/VoteJpaEntity.java 생성자에 likeCount, commentCount 파라미터 추가
.../feed/adapter/out/mapper/FeedMapper.java, .../record/adapter/out/mapper/RecordMapper.java, .../vote/adapter/out/mapper/VoteMapper.java 매핑 로직에 likeCount, commentCount 필드 추가
.../feed/domain/Feed.java, .../record/domain/Record.java, .../vote/domain/Vote.java 도메인에 likeCount, commentCount 필드 추가 및 기본값 0 지정, static factory method 수정
.../record/adapter/in/web/RecordQueryController.java 조회 API 파라미터 구조 변경 및 isOverview 추가
.../record/adapter/in/web/response/RecordDto.java, .../VoteDto.java, .../RecordSearchResponse.java DTO 구조 및 팩토리 메서드 변경, 인터페이스명 및 타입명 변경
.../record/adapter/out/persistence/RecordQueryPersistenceAdapter.java, .../RecordQueryRepository.java, .../RecordQueryRepositoryImpl.java 동적 쿼리, 페이징, 정렬, 타입 필터링 등 Querydsl 기반으로 전면 리팩토링
.../record/adapter/out/persistence/RecordSearchSortParams.java, .../RecordSearchTypeParams.java 정렬/타입 파라미터 enum 신설 및 검증 로직 추가
.../record/application/port/in/dto/RecordSearchQuery.java DTO 파일 삭제(더 이상 사용하지 않음)
.../record/application/port/in/dto/RecordSearchUseCase.java, .../service/RecordSearchService.java, .../port/out/RecordQueryPort.java 쿼리 DTO → 명시적 파라미터로 변경, 서비스 로직 전체 리팩토링
.../vote/domain/VoteItem.java percentage 계산 관련 TODO 주석 추가(로직 변경 없음)
.../record/adapter/in/web/RecordQueryControllerTest.java, .../RecordQueryRepositoryImplTest.java, .../application/service/RecordSearchServiceTest.java 테스트 코드 전체 리팩토링 및 파라미터 구조, 검증, 페이징/정렬 테스트 강화

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant PersistenceAdapter
    participant Repository

    Client->>Controller: viewRecordList(roomId, type, sort, pageStart, pageEnd, isOverview, pageNum, userId)
    Controller->>Service: search(roomId, type, sort, pageStart, pageEnd, isOverview, pageNum, userId)
    Service->>PersistenceAdapter: findRecordsByRoom(roomId, type, pageStart, pageEnd, isOverview, userId, pageable)
    PersistenceAdapter->>Repository: findRecordsByRoom(roomId, viewType, pageStart, pageEnd, isOverview, userId, pageable)
    Repository-->>PersistenceAdapter: Page<RecordSearchResult>
    PersistenceAdapter-->>Service: Page<RecordSearchResult>
    Service-->>Controller: RecordSearchResponse
    Controller-->>Client: BaseResponse<RecordSearchResponse>
Loading

Assessment against linked issues

Objective (Issue) Addressed Explanation
Querydsl 동적 쿼리 활용 (#63)
기록장 조회 시 정렬 오류 수정 (#63)

Assessment against linked issues: Out-of-scope changes

해당 이슈 범위를 벗어난 변경사항은 발견되지 않았습니다.

Possibly related PRs

  • THIP-TextHip/THIP-Server#46: BookCommandPersistenceAdapter, BookCommandPort에 Book ID 기반 조회 메서드 추가. 본 PR과 동일 클래스 및 인터페이스에 대한 확장이나, 조회 기준이 다릅니다.
  • THIP-TextHip/THIP-Server#60: DateUtil 클래스 변경 관련 PR로, 본 PR과 동일 클래스에 대한 변경이지만 기능 및 변경 범위가 다릅니다.

Poem

🐇
기록장 쿼리, 새 옷을 입었네
정렬도 페이징도, 동적으로 척척!
타입별로 골라보고, 좋아요 수도 세고
Querydsl이 춤추는 봄날의 밤
테스트 토끼도 깡총깡총
코드가 더 똑똑해졌어요!

"""


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f6b8e43 and 88f021c.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/feed/domain/Feed.java (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 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.

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

🧹 Nitpick comments (6)
src/main/java/konkuk/thip/vote/domain/VoteItem.java (1)

28-31: 퍼센트 계산 알고리즘 개선이 필요합니다.

TODO 주석이 정확한 문제를 지적하고 있습니다. 현재 반올림 방식은 전체 퍼센트 합이 100%가 되지 않을 수 있습니다. 가장 큰 나머지를 가진 항목에 차이를 할당하는 "Largest Remainder Method"를 고려해보세요.

이 문제를 해결하는 알고리즘을 구현해드릴까요? 아니면 별도 이슈로 등록하여 추적하시겠습니까?

src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)

7-7: 명시적 파라미터 사용으로 타입 안전성 향상

쿼리 객체에서 명시적 파라미터로 변경한 것은 타입 안전성과 검증 측면에서 좋은 개선입니다. 특히 isOverview 파라미터 추가는 PR 목표와 일치합니다.

다만 파라미터가 7개로 많아졌으므로, 향후 관련 파라미터들을 그룹핑하는 것을 고려해보세요:

public record SearchCriteria(Long roomId, String type, String sort, Boolean isOverview) {}
public record PageCriteria(Integer pageStart, Integer pageEnd, Integer pageNum) {}

RecordSearchResponse search(SearchCriteria criteria, PageCriteria page, Long userId);
src/main/java/konkuk/thip/vote/domain/Vote.java (1)

41-42: withoutId 메서드의 필드 설정 일관성 확인

새로운 필드들이 명시적으로 0으로 설정되어 일관성이 유지되고 있습니다. 하지만 @Builder.Default가 이미 설정되어 있으므로 명시적 설정이 필요한지 검토해보세요.

다음과 같이 간소화할 수 있습니다:

 public static Vote withoutId(String content, Long creatorId, Integer page, boolean isOverview, Long roomId) {
     return Vote.builder()
             .id(null)
             .content(content)
             .creatorId(creatorId)
             .page(page)
             .isOverview(isOverview)
-            .likeCount(0)
-            .commentCount(0)
             .roomId(roomId)
             .build();
 }
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (1)

17-18: 메서드 파라미터 개수 검토 필요

메서드에 6개의 파라미터가 있어 복잡성이 증가했습니다. 파라미터들을 관련 그룹으로 묶은 DTO나 Value Object 사용을 고려해보세요.

예를 들어, 검색 조건들을 하나의 DTO로 묶을 수 있습니다:

public record RecordSearchCriteria(
    String type, 
    Integer pageStart, 
    Integer pageEnd, 
    Boolean isOverview
) {}

@Override
public Page<RecordSearchResponse.RecordSearchResult> findRecordsByRoom(
    Long roomId, 
    RecordSearchCriteria criteria, 
    Long userId, 
    Pageable pageable
) {
    return recordJpaRepository.findRecordsByRoom(
        roomId, 
        criteria.type(), 
        criteria.pageStart(), 
        criteria.pageEnd(), 
        criteria.isOverview(), 
        userId, 
        pageable
    );
}
src/main/java/konkuk/thip/feed/domain/Feed.java (1)

42-43: withoutId 메서드의 새로운 필드 처리 확인

새로운 필드들이 적절히 설정되었습니다. Vote 도메인과 마찬가지로 @Builder.Default가 이미 설정되어 있으므로 명시적 설정의 필요성을 검토해보세요.

src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (1)

28-43: 불변 객체 업데이트 메서드 개선

withIsLikedAndVoteItems 메서드는 불변 객체의 특정 필드를 업데이트하는 좋은 패턴입니다. 하지만 메서드명이 길고 구체적이므로 더 일반적인 이름을 고려해보세요.

다음과 같이 더 간결한 이름을 사용할 수 있습니다:

-public VoteDto withIsLikedAndVoteItems(boolean isLiked, List<VoteItemDto> voteItems) {
+public VoteDto withLikeStatusAndVoteItems(boolean isLiked, List<VoteItemDto> voteItems) {

또는 Builder 패턴을 활용한 접근도 고려할 수 있습니다:

public VoteDto withUpdates(boolean isLiked, List<VoteItemDto> voteItems) {
    return this.toBuilder()
        .isLiked(isLiked)
        .voteItems(voteItems)
        .build();
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f9ed731 and cf958bf.

📒 Files selected for processing (30)
  • src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/common/util/DateUtil.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/mapper/FeedMapper.java (2 hunks)
  • src/main/java/konkuk/thip/feed/domain/Feed.java (3 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (2 hunks)
  • src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/mapper/RecordMapper.java (2 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchSortParams.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchTypeParams.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java (0 hunks)
  • src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1 hunks)
  • src/main/java/konkuk/thip/record/domain/Record.java (3 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/mapper/VoteMapper.java (2 hunks)
  • src/main/java/konkuk/thip/vote/domain/Vote.java (3 hunks)
  • src/main/java/konkuk/thip/vote/domain/VoteItem.java (1 hunks)
  • src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java (2 hunks)
  • src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (3 hunks)
  • src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (2 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (2)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
🧬 Code Graph Analysis (3)
src/main/java/konkuk/thip/record/domain/Record.java (2)
src/main/java/konkuk/thip/feed/domain/Feed.java (1)
  • Getter (10-49)
src/main/java/konkuk/thip/vote/domain/Vote.java (1)
  • Getter (12-69)
src/main/java/konkuk/thip/vote/domain/Vote.java (1)
src/main/java/konkuk/thip/record/domain/Record.java (1)
  • Getter (12-75)
src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java (1)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
  • DateUtil (7-41)
⏰ 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 (40)
src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (2)

26-28: 좋아요/댓글 수 필드 추가가 적절합니다.

likeCountcommentCount 필드가 기본값 0으로 적절히 초기화되어 있고, 생성자도 올바르게 업데이트되었습니다.


34-39: 하위 클래스 생성자 매개변수 순서 일치 확인 완료

  • FeedJpaEntity, RecordJpaEntity, VoteJpaEntity 모두
    super(content, likeCount, commentCount, userJpaEntity) 호출로 순서가 일치합니다.
  • 추가 수정 불필요합니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchTypeParams.java (1)

21-28: enum 변환 로직이 잘 구현되었습니다.

from() 메서드가 적절한 예외 처리와 함께 깔끔하게 구현되었습니다. 다만 위에서 언급한 예외 타입만 수정하면 됩니다.

src/main/java/konkuk/thip/common/util/DateUtil.java (1)

10-17: 유틸리티 메서드의 static 변환이 적절합니다.

DateUtil 클래스를 순수 유틸리티 클래스로 만든 것은 좋은 리팩토링입니다. Spring 의존성을 제거하고 static 메서드로 변환한 것이 적절합니다.

src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java (1)

16-16: CQRS 패턴에 맞는 메서드 추가입니다.

findBookByRoomId 메서드 추가가 프로젝트의 CQRS Port 분리 컨벤션에 적합합니다. CommandPort에서 도메인 엔티티를 조회하는 findByXXX 패턴을 잘 따르고 있습니다.

src/main/java/konkuk/thip/vote/adapter/out/mapper/VoteMapper.java (1)

18-19: 매핑 로직이 올바르게 구현되었습니다.

likeCountcommentCount 필드가 양방향 매핑에서 일관되게 추가되어 도메인 엔티티와 JPA 엔티티 간의 데이터 무결성을 보장합니다.

Also applies to: 31-32

src/main/java/konkuk/thip/feed/adapter/out/mapper/FeedMapper.java (1)

22-23: 일관된 매핑 패턴이 적용되었습니다.

다른 Post 타입 매퍼들과 동일한 방식으로 likeCountcommentCount 필드가 추가되어 코드베이스 전반의 일관성을 유지합니다.

Also applies to: 35-36

src/main/java/konkuk/thip/record/adapter/out/mapper/RecordMapper.java (1)

15-16: 매퍼 클래스 간 일관성이 우수합니다.

Record, Feed, Vote 매퍼 모두에서 동일한 패턴으로 likeCountcommentCount 필드가 추가되어 코드의 일관성과 유지보수성을 향상시킵니다.

Also applies to: 32-33

src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java (2)

8-8: 새로운 의존성 추가가 적절합니다.

RoomJpaRepository 의존성과 관련 에러 코드 import가 새로운 메서드 구현에 필요한 최소한의 변경사항입니다.

Also applies to: 13-13, 19-19


56-62: CQRS 컨벤션을 올바르게 준수합니다.

Retrieved learning에 따르면 CommandPort에 findByXXX 메서드를 추가하는 것이 THIP 프로젝트의 컨벤션입니다. 메서드 구현도 적절한 예외 처리와 함께 올바르게 되어 있습니다.

src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java (1)

29-30: 생성자 수정이 올바르게 구현되었습니다.

매퍼 클래스들에서 추가된 likeCountcommentCount 필드와 일치하게 생성자가 수정되어 데이터 흐름의 일관성을 보장합니다.

src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchSortParams.java (1)

11-31: 정렬 파라미터 enum 구현 우수

문자열 기반 정렬 파라미터를 enum으로 관리하는 것은 타입 안전성과 유지보수성 측면에서 훌륭한 개선입니다. 각 정렬 옵션이 명확히 정의되어 있고, 검증 로직도 적절합니다.

src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java (1)

29-30: likeCount, commentCount 필드 추가 승인

상위 클래스 PostJpaEntity에 추가된 likeCount, commentCount 필드를 생성자에 반영한 일관성 있는 변경입니다. 컴파일 타임 안전성도 확보되었습니다.

src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (1)

31-32: 일관성 있는 JPA 엔티티 업데이트

VoteJpaEntity와 동일한 패턴으로 likeCount, commentCount 필드를 생성자에 추가한 일관성 있는 변경입니다.

src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (2)

3-5: Spring Data 활용으로 페이징 기능 개선

PagePageable 사용으로 페이징 메타데이터까지 포함한 완전한 페이징 솔루션을 제공하게 되었습니다. 이는 PR 목표인 "DB 쿼리에서 직접 offset 기반 페이징" 구현에 부합합니다.


9-9: CQRS 컨벤션 준수 및 isOverview 파라미터 추가 우수

RecordSearchResponse.RecordSearchResult를 반환하는 것은 프로젝트의 CQRS 컨벤션(QueryPort에는 조회 API response 데이터 조회 메서드)을 잘 준수합니다. 또한 isOverview 파라미터 추가로 validation 문제도 해결되었습니다.

src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (3)

22-23: 파라미터 유연성 개선됨

typesort 파라미터를 선택적으로 변경한 것은 API 사용성을 높이는 좋은 개선사항입니다.


26-26: 필수 isOverview 파라미터 추가 확인

새로운 isOverview 파라미터가 필수로 추가되었습니다. 이는 PR 목표에서 언급한 검증 조정 요구사항을 해결합니다.


30-30: 메서드 호출 방식 개선됨

DTO 객체 대신 개별 파라미터를 직접 전달하는 방식으로 변경되어 더 명시적이고 유연해졌습니다.

src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java (2)

18-18: 인터페이스 구현 변경 승인됨

RecordSearchResponse.RecordSearchResult 인터페이스 구현으로 변경되어 다형성 지원이 개선되었습니다.


24-38: 불변성 패턴 개선됨

정적 팩토리 메서드 대신 인스턴스 메서드 withIsLiked를 사용하는 방식은 불변성을 더 잘 보장하며, 상태 변경을 안전하게 처리합니다.

src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (2)

3-5: Spring Data 페이징 지원 추가됨

PagePageable 임포트 추가로 Spring Data의 표준 페이징 기능을 활용하게 되었습니다.


9-9: 페이징 처리 대폭 개선됨

메서드 시그니처가 Page<RecordSearchResponse.RecordSearchResult>를 반환하도록 변경되어 PR 목표인 페이징 처리 개선을 달성했습니다. Pageable 파라미터로 데이터베이스 레벨에서 직접 페이징 처리가 가능해져 성능이 크게 향상됩니다.

src/main/java/konkuk/thip/record/domain/Record.java (4)

5-5: Builder import 추가됨

@Builder.Default 애노테이션 사용을 위한 import가 적절히 추가되었습니다.


9-10: 에러 코드 import 추가됨

검증 메서드에서 사용할 에러 코드들이 명시적으로 import되어 가독성이 향상되었습니다.


26-30: 좋아요/댓글 수 필드 추가됨

likeCountcommentCount 필드가 @Builder.Default로 기본값 0으로 설정되어 적절히 추가되었습니다. 이는 VoteFeed 엔티티의 패턴과 일치하여 일관성을 유지합니다.


46-47: 팩토리 메서드 업데이트 적절함

withoutId 메서드에서 새로운 count 필드들을 명시적으로 0으로 설정하는 것이 적절합니다. 기본값에 의존하지 않고 명시적으로 설정하여 의도를 명확히 했습니다.

src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java (5)

113-114: 테스트 데이터에 count 필드 설정됨

RecordJpaEntity 생성 시 likeCountcommentCount를 명시적으로 설정하여 새로운 도메인 필드를 테스트에 반영했습니다.


122-123: Vote 엔티티에도 count 필드 설정됨

VoteJpaEntity에도 동일하게 count 필드들이 설정되어 일관성을 유지합니다.


148-148: isOverview 파라미터 테스트에 추가됨

새로운 필수 파라미터인 isOverview가 테스트 요청에 적절히 추가되었습니다.


156-161: Vote 노드 검증 먼저 수행됨

응답 검증에서 vote 노드를 먼저 검증하도록 순서가 변경되었고, DateUtil.formatBeforeTime 정적 메서드 사용으로 업데이트되었습니다.


169-177: Record 노드 검증 및 count 필드 확인됨

Record 노드에 대한 검증이 추가되었고, 새로운 likeCountcommentCount 필드가 테스트 데이터와 일치하는지 적절히 검증됩니다.

src/main/java/konkuk/thip/vote/domain/Vote.java (2)

26-30: likeCount와 commentCount 필드 추가 승인

새로운 카운트 필드들이 적절하게 추가되었습니다. @Builder.Default를 사용하여 기본값을 0으로 설정한 것이 좋습니다.


5-5: 중복된 @builder 사용 없음 – @SuperBuilder + @Builder.Default 조합은 의도된 패턴입니다

Vote.java 클래스 레벨에는 @SuperBuilder만 적용되어 있고, likeCount/commentCount 필드의 기본값 설정을 위해 @Builder.Default를 사용하고 있습니다.
이 패턴은 다른 도메인 엔티티(Record, Feed 등)에도 동일하게 적용된 Lombok 권장 방식이며, 별도의 @builder 어노테이션 추가나 변경은 불필요합니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (1)

9-9: 인터페이스명 변경으로 가독성 개선

PostDto에서 RecordSearchResult로 인터페이스명이 변경되어 더 명확한 의미를 제공합니다. 검색 결과의 목적을 더 잘 표현하는 좋은 개선사항입니다.

Also applies to: 16-16, 29-29

src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (1)

17-18: 어댑터 단순화 개선사항 승인

복잡한 매핑 로직을 repository 레이어로 이동시켜 어댑터의 역할을 단순화한 것은 좋은 개선사항입니다. 관심사의 분리가 잘 이루어졌습니다.

src/main/java/konkuk/thip/feed/domain/Feed.java (1)

22-29: 카운트 필드들의 일관성 있는 추가

reportCountInteger로 변경하고 likeCount, commentCount 필드를 추가한 것이 다른 도메인 엔티티들과 일관성을 유지합니다. @Builder.Default를 사용한 초기화도 적절합니다.

src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (1)

22-22: 인터페이스 변경 승인

RecordSearchResponse.RecordSearchResult 인터페이스로의 변경이 다른 파일들과 일관성을 유지합니다.

src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)

3-162: 테스트 커버리지가 포괄적으로 잘 구성되었습니다!

파라미터 검증과 주요 성공 시나리오를 모두 포함하여 서비스 로직을 효과적으로 검증하고 있습니다.

src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)

29-164: 리포지토리 테스트가 체계적으로 잘 구성되었습니다!

페이징, 필터링, 정렬, overview 기능을 모두 검증하고 있으며, 테스트 데이터 설정도 적절합니다.

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

♻️ Duplicate comments (1)
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)

3-3: JDI 패키지의 예외 클래스를 잘못 사용하고 있습니다.

com.sun.jdi.request.InvalidRequestStateException은 Java Debug Interface용 클래스입니다. 일반 애플리케이션 코드에서는 사용하면 안 됩니다.

다음과 같이 수정하세요:

-import com.sun.jdi.request.InvalidRequestStateException;

그리고 120번째 줄의 예외 생성 부분을 다음과 같이 수정하세요:

-throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new InvalidRequestStateException("pageNum은 1 이상의 값이어야 합니다."));
+throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new IllegalArgumentException("pageNum은 1 이상의 값이어야 합니다."));

Also applies to: 120-120

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)

45-45: 메서드 파라미터가 너무 많습니다.

현재 8개의 파라미터를 받고 있어 가독성과 유지보수성이 떨어집니다. Request DTO 또는 Parameter Object 패턴 사용을 고려해보세요.

예시:

public record RecordSearchParams(
    Long roomId,
    String type,
    String sort,
    Integer pageStart,
    Integer pageEnd,
    Boolean isOverview,
    Integer pageNum,
    Long userId
) {}
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)

41-57: BooleanBuilder 로직이 복잡합니다.

Record와 Vote 엔티티를 위한 조건이 반복되고 있습니다. 헬퍼 메서드로 분리하여 가독성을 개선할 수 있습니다.

private BooleanBuilder buildRecordCondition(QRecordJpaEntity record, Long roomId, Boolean isOverview, Integer pageStart, Integer pageEnd) {
    BooleanBuilder builder = new BooleanBuilder();
    builder.and(record.roomJpaEntity.roomId.eq(roomId));
    
    if (isOverview) {
        builder.and(record.isOverview.isTrue());
    } else {
        builder.and(record.isOverview.isFalse());
        if (pageStart != null && pageEnd != null) {
            builder.and(record.page.between(pageStart, pageEnd));
        }
    }
    return builder;
}

private BooleanBuilder buildVoteCondition(QVoteJpaEntity vote, Long roomId, Boolean isOverview, Integer pageStart, Integer pageEnd) {
    // Vote에 대한 유사한 로직
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cf958bf and a9edaf4.

📒 Files selected for processing (5)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchSortParams.java (1 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchTypeParams.java (1 hunks)
  • src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1 hunks)
  • src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchTypeParams.java
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordSearchSortParams.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
⏰ 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 (5)
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (2)

103-113: 페이지 파라미터 검증 로직이 적절합니다.

pageStart/pageEnd와 isOverview 간의 관계를 명확하게 검증하고 있으며, 적절한 예외 타입을 사용하고 있습니다.


125-131: 정렬 로직이 개선되었습니다.

switch 표현식을 사용한 명확한 정렬 매핑과 Spring Data Sort 활용이 좋습니다.

src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)

59-75: 정렬 로직이 올바르게 구현되었습니다.

이전 리뷰의 피드백이 반영되어 엔티티의 likeCount와 commentCount 필드를 직접 사용하고 있습니다. Record와 Vote의 count를 합산하는 방식도 적절합니다.


87-124: DTO 매핑 로직이 명확합니다.

엔티티 타입별로 적절한 DTO로 변환하고 있으며, isLiked와 voteItems를 서비스 계층에서 처리하도록 한 설계가 좋습니다.

src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)

43-202: 테스트가 포괄적이고 잘 구성되어 있습니다.

다양한 시나리오(페이징, 필터링, 정렬)를 모두 검증하고 있으며, 테스트 데이터 설정도 적절합니다. 특히 likeCount와 commentCount를 다르게 설정하여 정렬 테스트의 신뢰성을 높인 점이 좋습니다.

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

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)

78-86: 다중 조인으로 인한 성능 우려

PR에서 언급하신 대로 여러 테이블 조인으로 인한 성능 문제가 우려됩니다. 현재 구조에서는 불가피하지만, 다음과 같은 최적화 방안을 고려해보세요:

  1. 인덱스 최적화: roomId, isOverview, page 필드에 복합 인덱스 추가
  2. 쿼리 힌트: 필요시 @QueryHints를 사용하여 fetch 전략 최적화
  3. 읽기 전용 트랜잭션: 조회 메서드에 @Transactional(readOnly = true) 적용

향후 계획하신 단일 테이블 통합 리팩토링이 근본적인 해결책이 될 것 같습니다.


127-136: 쿼리 로직 중복 제거를 위한 리팩토링 제안

데이터 조회와 총 개수 조회에서 동일한 조인과 where 조건이 중복됩니다. 유지보수성 향상을 위해 공통 로직을 추출하는 것을 고려해보세요:

+private BooleanBuilder buildWhereCondition(Long roomId, String viewType, Integer pageStart, 
+                                           Integer pageEnd, Boolean isOverview, Long loginUserId) {
+    // 기존 where 조건 생성 로직을 여기로 이동
+}
+
+private <T> JPAQuery<T> createBaseQuery(JPAQueryFactory queryFactory) {
+    return queryFactory.from(post)
+            .leftJoin(record).on(post.postId.eq(record.postId))
+            .leftJoin(vote).on(post.postId.eq(vote.postId));
+}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcff14 and eb31143.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
⏰ 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/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)

65-76: 정렬 로직이 잘 개선되었습니다!

PR 목표에 맞게 정렬 로직이 개선되어 문자열 기반 날짜 정렬 문제가 해결되었습니다. coalesce(0)를 사용한 null 처리도 적절합니다.


105-106: VoteItem 로딩 시 N+1 쿼리 문제 확인 필요

VoteItem을 서비스 레벨에서 별도로 로딩한다면 N+1 쿼리 문제가 발생할 수 있습니다. 성능 최적화를 위해 다음을 확인해주세요:

  • VoteItem을 batch fetch로 한 번에 로딩하는지
  • 또는 별도의 쿼리로 VoteItem 목록을 미리 조회하여 매핑하는지

필요시 @BatchSize 어노테이션이나 fetch join을 고려해보세요.

Also applies to: 118-118

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

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)

123-129: 정렬 로직이 올바르게 구현되었습니다.

coalesce를 사용한 null 안전 처리와 두 엔티티의 count 합산이 적절합니다. 다만, likeCountcommentCount로 정렬이 빈번하다면 데이터베이스에 복합 인덱스 추가를 고려해보세요.


52-60: 쿼리 성능 모니터링을 권장합니다.

PR에서 언급하신 대로 다중 조인으로 인한 성능 이슈가 우려됩니다. 향후 단일 테이블 리팩토링 전까지는 다음을 고려해보세요:

  • 쿼리 실행 계획 분석
  • 적절한 인덱스 추가
  • 대용량 데이터에서의 성능 테스트
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between eb31143 and f6b8e43.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
⏰ 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 (3)
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (3)

37-37: 메서드 시그니처 변경이 적절합니다.

페이징 처리를 위해 Page 반환 타입과 Pageable 파라미터를 사용한 것은 Spring Data의 표준 방식을 잘 따르고 있습니다. isOverview 파라미터 추가로 전체 페이지 범위 조회 문제도 해결됩니다.


42-49: 동적 쿼리 구성이 개선되었습니다.

헬퍼 메서드를 사용하여 각 엔티티 타입별로 조건을 분리한 것이 좋습니다. 이전 리뷰에서 지적된 Boolean 조건 우선순위 문제가 올바르게 해결되었습니다.


62-99: 엔티티 매핑 로직이 명확하게 구현되었습니다.

타입별 매핑과 예외 처리가 적절합니다. 서비스 레벨에서 처리될 부분들(isLiked, voteItems)을 주석으로 명시한 것도 좋습니다.

Copy link
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

기록장 조회의 화면설계서와 비교했을 때 잘 이해되지않는 부분이 있어 리뷰 남겼습니다! 확인 부탁드립니다!

추가로 현재 코드에서는 칩 선택 X, 혹은 페이지별 보기 칩 선택하였을 때 유저의 진행도보다 이후의 페이지 기록에 대한 블러처리는 구현되지 않은 상태인데, 이 부분도 확인해주시면 감사하겠습니다!


//마지막 활동 시간 포맷팅 -> ex. 1분 전, 1시간 전, 1일 전
public String formatLastActivityTime(LocalDateTime createdAt) {
public static String formatBeforeTime(LocalDateTime createdAt) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

public FeedJpaEntity(String content, UserJpaEntity userJpaEntity, Boolean isPublic, int reportCount, BookJpaEntity bookJpaEntity) {
super(content, userJpaEntity);
public FeedJpaEntity(String content, Integer likeCount, Integer commentCount, UserJpaEntity userJpaEntity, Boolean isPublic, int reportCount, BookJpaEntity bookJpaEntity) {
super(content, likeCount, commentCount, userJpaEntity);
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +26 to +28
private Integer likeCount = 0;

private Integer commentCount = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

좋습니다! 이제 저희가 관련 C,U,D 할때 해당 데이터를 잘 업데이트 해줘야 하니 이 점 유의하면서 개발 진행해야할 것 같네요

Comment on lines 41 to 43
.reportCount(reportCount)
.likeCount(0)
.commentCount(0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3 : reportCount 가 Feed 신고 횟수를 의미하는거 같은데, withoutId 정적 팩토리 메서드는 최초 Feed를 생성하기 위한 메서드이니, reportCount 도 인자로 값을 주입받는게 아니라 내부적으로 0으로 초기화해주는게 어떤가요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 수정하겠습니다!

Comment on lines +103 to +104
if((pageStart != null && pageEnd == null) || (pageStart == null && pageEnd != null)) {
throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new IllegalArgumentException("pageStart와 pageEnd는 모두 설정되거나(특정 페이지 조회) 모두 설정되지 않아야 합니다.(전체 페이지 조회)"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

p2 : '총평 보기' 가 아니라 '페이지별 보기' 가 요청으로 들어오는 경우 pageStart, pageEnd 값이 nullable 한 현재의 유효성 검증 로직이 잘 이해되지 않습니다.

피그마의 화면설계서에 따르면

  1. 아무런 칩 선택 X
  2. 페이지별 보기 선택
  3. 총평 보기 선택

이렇게 3가지 경우가 가능한데, 현준님이 구현하신 isOverview == false && pageStart == null && pageEnd == null 은 어떤 경우에 해당하는 것인지 궁금합니다.

혹시 1번에 해당하는 경우를 구현하신 거라면, 가독성을 위해 request param을 다음과 같이 수정하는건 어떤가 제안드립니다

boolean pageFlag, boolean overviewFlag (네이밍은 그냥 생각나는대로 써본겁니다)
-> 둘다 false : 1번, pageFlag 만 true : 2번, overviewFlag 만 true : 3번

// service에서 request param의 유효성 처리

if(1,2,3   무조건  가지 경우에만 속해야 한다) : 아니면 문제있음

if (1 && pageStart, pageEnd 둘다 null이 아니다) : 문제 있음
-> 유효성 검사 통과하면 지금처럼 pageStart, pageEnd를 현재처럼 1, book.pageCount  수정

if (2) : pageStart, pageEnd  대소 비교

Copy link
Contributor Author

@buzz0331 buzz0331 Jul 13, 2025

Choose a reason for hiding this comment

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

말씀해주신 것처럼 화면 설계 기준으로는 총 세 가지 경우(1. 아무런 칩 선택 X, 2. 페이지별 보기 선택, 3. 총평 보기 선택)로 나뉠 수 있으며, 각 상황에 맞는 명확한 파라미터 구분이 필요하다는 점에 공감합니다.

제가 pm님께 여쭤보았을 때, ‘아무런 칩 선택 X’의 경우를 페이지별 기록 전체(0~전체 페이지) + 그룹 기록 필터링 / 최신순 정렬답변주셨습니다. 이때 프론트엔드에서도 pageStart와 pageEnd 값을 null로 요청하게 되어, 총평 보기와 동일한 형태의 요청 구조가 됩니다.

따라서 이러한 상황을 구분하기 위해 isOverview라는 boolean flag를 도입하였고, 이 flag만으로도 ‘페이지별 보기’와 ‘총평 보기’ 두 칩 선택 여부를 명확히 판별할 수 있다고 판단하여 현재 구조로 구현하였습니다.

즉, 현재 구조에서는 다음과 같이 동작합니다:
• isOverview == true인 경우: 총평 보기
• isOverview == false이면서 pageStart, pageEnd == null: 아무런 칩 선택 → 전체 페이지 대상으로 필터링
• isOverview == false이면서 pageStart, pageEnd != null: 페이지별 보기

혹시라도 칩을 모두 선택하지 않았을 경우, 총평과 페이지 기록을 모두 보여주어야 하는 요구사항으로 변경된다면 성준님이 말씀하신 isPageFilter같은 flag를 도입하여야 할 것 같습니다!

추가적으로, 해당 API에 대한 상세한 동작 방식과 파라미터 처리 로직은 다음 노션 문서에 정리해두었습니다. 확인해주시면 감사드리겠습니다. 기록장 조회 api

.build();
}

//todo 총 퍼센트가 100이 되는 알고리즘으로 수정!
Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 알고리즘을 고민해봐야 겟네요

@buzz0331
Copy link
Contributor Author

기록장 조회의 화면설계서와 비교했을 때 잘 이해되지않는 부분이 있어 리뷰 남겼습니다! 확인 부탁드립니다!

추가로 현재 코드에서는 칩 선택 X, 혹은 페이지별 보기 칩 선택하였을 때 유저의 진행도보다 이후의 페이지 기록에 대한 블러처리는 구현되지 않은 상태인데, 이 부분도 확인해주시면 감사하겠습니다!

블러 처리를 깜빡했네요,, 수정해보겠습니다..

hd0rable
hd0rable previously approved these changes Jul 13, 2025
Copy link
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

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

수고하셨습니닷

@Builder
public FeedJpaEntity(String content, UserJpaEntity userJpaEntity, Boolean isPublic, int reportCount, BookJpaEntity bookJpaEntity) {
super(content, userJpaEntity);
public FeedJpaEntity(String content, Integer likeCount, Integer commentCount, UserJpaEntity userJpaEntity, Boolean isPublic, int reportCount, BookJpaEntity bookJpaEntity) {
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻👍🏻

.userId(userId)
.build()
));
return BaseResponse.ok(recordSearchUseCase.search(roomId, type, sort, pageStart, pageEnd, isOverview, pageNum, userId));
Copy link
Member

Choose a reason for hiding this comment

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

옷 RecordSearchQuery는 왜 빼신거에요?? 단순궁금

Copy link
Contributor Author

Choose a reason for hiding this comment

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

현재 pageNum과 같은 변수에서 유효성 검증 후 값을 직접 조정하는 로직이 있어서, 굳이 불변객체로 DTO를 하나 더 두지 않아도 된다고 판단했습니다!

@sonarqubecloud
Copy link

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-98] [refactor] 기록장 조회 api 로직 변경

3 participants