Skip to content

Verdict 도메인 N+1 문제 해결 및 리팩터링#79

Open
kkiseug wants to merge 6 commits into
mainfrom
feat/65-verdict-refactor
Open

Verdict 도메인 N+1 문제 해결 및 리팩터링#79
kkiseug wants to merge 6 commits into
mainfrom
feat/65-verdict-refactor

Conversation

@kkiseug
Copy link
Copy Markdown
Collaborator

@kkiseug kkiseug commented Mar 21, 2026

이슈

요약

  • n+1 문제를 해결합니다.

  • 이외에도 소소한 수정들이 있습니다.

    • User hashCode를 id 기반으로 생성하도록 변경
    • Valid 어노테이션 추가
  • 그리고 제안해주신 saveAll() 메서드의 경우 JPA의 Repository 인터페이스를 상속하고 있으면, saveAll을 자동으로 생성해주지 않더라고요!

  • 또한, saveAll 내부 구조가 결국 여러번 save를 호출하는 형태라 크게 다르지 않을 거 같다고 판단해서 그대로 두었습니다.

Summary by CodeRabbit

출시 노트

  • 버그 수정

    • 객체 비교/해시 일치로 컬렉션 처리 안정성 향상
  • 새로운 기능 / 유효성 검사

    • 심판 관련 요청의 입력 검증 적용으로 잘못된 요청 차단
    • 특정 필수 필드에 대한 명확한 오류 메시지 추가
  • 성능 개선

    • 관련 데이터 조회 시 연관 항목을 함께 불러와 응답 지연 감소

@kkiseug kkiseug requested a review from lyouxsun March 21, 2026 09:11
@kkiseug kkiseug self-assigned this Mar 21, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1d95779a-8393-4806-b2f0-9a18d621b139

📥 Commits

Reviewing files that changed from the base of the PR and between 268509b and 381bafc.

📒 Files selected for processing (1)
  • src/main/java/com/example/demo/domain/VerdictRepository.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/example/demo/domain/VerdictRepository.java

📝 Walkthrough

Walkthrough

이번 PR은 소비 심판 기능 리팩터링을 포함합니다. User.hashCode()가 id 기반(Objects.hash(getId()))으로 변경되어 equals와 일관되게 되었고, Verdict 엔티티가 BaseEntity를 상속하도록 선언이 변경되었습니다. VerdictRepository의 두 쿼리에 엔티티를 즉시 로드하기 위한 JOIN FETCH들이 추가되었고, VerdictController의 요청 바디 파라미터에 @Valid가 적용되었습니다. 또한 Request/Response DTO들에 @NotNull 제약과 불필요한 import 정리가 적용되었습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

리뷰 시 주목할 점

안내: 아래 항목들을 중심으로 검토하세요 — 친절하고 구체적으로 문제를 찾는 연습을 합시다.

  1. User.hashCode / equals 일관성
  • equals가 실제로 id 기반인지 재확인하세요. equals가 id 비교에 null-safe하게 구현되어 있어야 합니다.
  • getId()가 null일 때의 동작(예: transient 엔티티)을 테스트 케이스로 확인하세요(같은 영속성 생명주기 중 컬렉션 사용 시 중요).
  1. Verdict extends BaseEntity
  • BaseEntity가 제공하는 필드(예: id, 생성/수정일 등)와 매핑 어노테이션이 Verdict의 기존 매핑과 충돌하지 않는지 확인하세요.
  • 상속으로 인한 equals/hashCode 정책 변경이 필요한지 점검하세요(특히 BaseEntity가 equals/hashCode를 구현한 경우).
  1. JOIN FETCH 변경점 (주의)
  • findMyVerdicts, findJurorVerdicts에 추가된 JOIN FETCH는 N+1을 해결하지만 페이징과 함께 사용하면 잘못된 결과를 만들 수 있습니다. 이 메서드들이 페이징이나 대량 조회와 함께 사용되는지 확인하세요.
  • 불필요한 조인으로 인해 중복 행이 발생할 수 있으니 DISTINCT 필요 여부를 검토하세요.
  • 연관 관계의 깊이(ledgerEntry → user 등)를 항상 필요로 하는지 비즈니스 시나리오와 대조하세요.
  1. 컨트롤러/DTO 검증
  • @Valid가 적용된 컨트롤러 메서드가 실제로 검증 실패 시 적절한 예외 처리(바인딩 에러 → 400 반환)를 하는지 확인하세요.
  • 추가된 @NotNull 메시지(한국어)가 국제화 정책에 부합하는지 점검하세요(필요 시 메시지 소스 사용).
  1. 기타
  • 제거된 import는 문제 없음. 변경된 파일들에 대해 단위/통합 테스트가 통과하는지 확인하세요.
  • 마이그레이션이나 시리얼라이제이션 영향(엔티티 상속 변경으로 인한 JSON/DTO 변환 등)이 없는지 살펴보세요.

필요하면 위 항목 중 우려되는 부분을 지정해서 작은 수정 PR로 분리해 주세요.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 N+1 문제 해결 및 리팩터링이라는 핵심 변경사항을 명확하게 요약하고 있으며, 실제 코드 변경사항과 완벽하게 일치합니다.
Linked Issues check ✅ Passed 코드 변경사항들이 이슈 #65의 요구사항인 소비 심판 기능 리팩터링 및 N+1 문제 해결을 충족하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 N+1 문제 해결 및 리팩터링이라는 범위 내에 있으며, 불필요한 외부 변경은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/65-verdict-refactor

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 Verdict 도메인에서 발생하던 N+1 쿼리 문제를 해결하고, 전반적인 코드 리팩터링을 통해 애플리케이션의 성능과 안정성을 향상시키는 데 중점을 둡니다. 특히, JPA 쿼리 최적화와 함께 엔티티의 hashCode 구현을 개선하고, API 요청에 대한 유효성 검증 로직을 추가하여 견고한 시스템을 구축합니다.

Highlights

  • N+1 문제 해결: VerdictRepository의 쿼리에 JOIN FETCH를 추가하여 N+1 문제를 해결했습니다.
  • User 엔티티 hashCode 개선: User 엔티티의 hashCode 메서드를 id 기반으로 변경하여 객체 동일성 비교를 개선했습니다.
  • Verdict 엔티티 상속 구조 변경: Verdict 엔티티가 BaseEntity를 상속하도록 변경하여 공통 필드를 관리합니다.
  • API 요청 유효성 검사 강화: 컨트롤러의 요청 DTO에 @Valid@NotNull 어노테이션을 추가하여 입력 유효성 검사를 강화했습니다.
  • 코드 정리: 사용되지 않는 import 문을 제거하여 코드 정리 및 가독성을 높였습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 Verdict 도메인의 N+1 문제를 해결하고, hashCode 구현을 개선하며, 유효성 검사를 추가하는 등 여러 유용한 수정을 포함하고 있습니다. 전반적으로 코드 품질을 높이는 좋은 변경이라고 생각합니다.

다만, N+1 문제 해결을 위해 추가한 JOIN FETCH 구문이 일부 케이스에서 모든 연관 관계를 포함하지 않아 추가적인 개선이 필요해 보입니다. 자세한 내용은 VerdictRepository.java 파일에 남긴 리뷰 코멘트를 참고해주세요.

Comment thread src/main/java/com/example/demo/domain/VerdictRepository.java Outdated
Comment thread src/main/java/com/example/demo/domain/VerdictRepository.java Outdated
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/example/demo/domain/User.java (1)

73-84: ⚠️ Potential issue | 🟡 Minor

equals()hashCode() 일관성 개선 - 좋은 수정입니다!

기존 getClass().hashCode()에서 Objects.hash(getId())로 변경하여 equals()와 일관성을 맞추셨네요.

다만, JPA 엔티티에서 id 기반 hashCode()는 잠재적인 문제가 있습니다:

  1. 새 엔티티(id == null)가 HashSet에 추가된 후 영속화되면 id가 할당되어 hashCode()가 변경됩니다.
  2. 이는 hashCode() 계약 위반으로, 컬렉션에서 해당 객체를 찾지 못하는 버그가 발생할 수 있습니다.

Hibernate 팀에서 권장하는 패턴은 상수 hashCode를 반환하는 것입니다:

💡 안정적인 hashCode 패턴 제안
 `@Override`
 public int hashCode() {
-    return Objects.hash(getId());
+    // JPA 엔티티의 안정적인 hashCode - id 변경에 영향받지 않음
+    return getClass().hashCode();
 }

또는 현재 구현을 유지하되, 새 엔티티를 HashSet/HashMap에 추가하기 전에 반드시 영속화하는 규칙을 팀 내에서 합의하시면 됩니다.

참고: Vlad Mihalcea - equals and hashCode

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/example/demo/domain/User.java` around lines 73 - 84, The
current User.equals(Object) compares by id but hashCode() uses
Objects.hash(getId()), which changes when a transient entity is persisted;
update the User.hashCode() implementation so it returns a stable value for newly
created entities (e.g., return a fixed constant) or return Objects.hash(getId())
only when getId() != null and a constant otherwise, ensuring the User.equals and
User.hashCode methods remain consistent for JPA entities.
🧹 Nitpick comments (1)
src/main/java/com/example/demo/infrastructure/controller/dto/RequestJudgeWebRequest.java (1)

5-8: @NotNull에 커스텀 메시지 추가를 권장합니다.

VerdictJudgeWebRequestverdictType에는 @NotNull(message = "심판 유형(type)은 필수입니다.")처럼 명확한 메시지가 있는데, 이 DTO에는 기본 메시지만 사용됩니다.

API 사용자 경험과 팀 내 일관성을 위해 커스텀 메시지 추가를 고려해 보세요.

♻️ 커스텀 메시지 추가 제안
 public record RequestJudgeWebRequest(
-    `@NotNull`
+    `@NotNull`(message = "가계부 항목 ID(ledgerEntryId)는 필수입니다.")
     Long ledgerEntryId
 ) {
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/demo/infrastructure/controller/dto/RequestJudgeWebRequest.java`
around lines 5 - 8, RequestJudgeWebRequest 레코드의 ledgerEntryId 필드에 기본 `@NotNull`
메시지 대신 일관된 커스텀 메시지를 추가하세요; RequestJudgeWebRequest 레코드 선언의 Long ledgerEntryId에
`@NotNull`(message = "원장 항목 ID(ledgerEntryId)는 필수입니다.")와 같이 명확한 한국어 메시지를 적용해
VerdictJudgeWebRequest의 verdictType과 동일한 스타일로 사용자 친화적 오류 메시지를 제공하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/java/com/example/demo/domain/User.java`:
- Around line 73-84: The current User.equals(Object) compares by id but
hashCode() uses Objects.hash(getId()), which changes when a transient entity is
persisted; update the User.hashCode() implementation so it returns a stable
value for newly created entities (e.g., return a fixed constant) or return
Objects.hash(getId()) only when getId() != null and a constant otherwise,
ensuring the User.equals and User.hashCode methods remain consistent for JPA
entities.

---

Nitpick comments:
In
`@src/main/java/com/example/demo/infrastructure/controller/dto/RequestJudgeWebRequest.java`:
- Around line 5-8: RequestJudgeWebRequest 레코드의 ledgerEntryId 필드에 기본 `@NotNull` 메시지
대신 일관된 커스텀 메시지를 추가하세요; RequestJudgeWebRequest 레코드 선언의 Long ledgerEntryId에
`@NotNull`(message = "원장 항목 ID(ledgerEntryId)는 필수입니다.")와 같이 명확한 한국어 메시지를 적용해
VerdictJudgeWebRequest의 verdictType과 동일한 스타일로 사용자 친화적 오류 메시지를 제공하도록 수정하세요.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15ad8459-e6a3-4d66-913b-d3903519792d

📥 Commits

Reviewing files that changed from the base of the PR and between 170948b and 268509b.

📒 Files selected for processing (8)
  • src/main/java/com/example/demo/domain/User.java
  • src/main/java/com/example/demo/domain/Verdict.java
  • src/main/java/com/example/demo/domain/VerdictRepository.java
  • src/main/java/com/example/demo/infrastructure/controller/VerdictController.java
  • src/main/java/com/example/demo/infrastructure/controller/dto/JurorVerdictWebResponse.java
  • src/main/java/com/example/demo/infrastructure/controller/dto/JurorVerdictsWebResponse.java
  • src/main/java/com/example/demo/infrastructure/controller/dto/RequestJudgeWebRequest.java
  • src/main/java/com/example/demo/infrastructure/controller/dto/VerdictJudgeWebRequest.java
💤 Files with no reviewable changes (2)
  • src/main/java/com/example/demo/infrastructure/controller/dto/JurorVerdictWebResponse.java
  • src/main/java/com/example/demo/infrastructure/controller/dto/JurorVerdictsWebResponse.java

Copy link
Copy Markdown
Collaborator

@lyouxsun lyouxsun left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!


Verdict save(Verdict verdict);

Optional<Verdict> findById(Long verdictId);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

VerdictService.getVerdict() 에서 findById로 엔티티를 조회한 후, Verdict 내부 User, LedgerEntry에 접근하여 여전히 N+1 문제가 발생합니다. findById를 다음과 같이 수정하면 성능이 더 개선될 것 같습니다!

Suggested change
@Query("""
SELECT v FROM Verdict v
JOIN FETCH v.ledgerEntry le
JOIN FETCH le.user
WHERE v.id = :verdictId
""")
Optional<Verdict> findByIdWithLedgerEntryAndUser(@Param("verdictId") Long verdictId);

import jakarta.validation.constraints.NotNull;

public record RequestJudgeWebRequest(
@NotNull
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이 어노테이션에도 오류 메시지가 있으면 좋을 것 같습니다

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.

[Refactor] 소비 심판 기능 리팩터링

2 participants