Skip to content

Conversation

@2ghrms
Copy link
Member

@2ghrms 2ghrms commented Dec 3, 2025

📝작업 내용

작업한 내용을 작성해주세요.

  • /v1/business-plans/{planId}/subsections API에 대해서 쿼리 실행계획 검토
  • 서브섹션 한번에 조회하도록 수정

🔎코드 설명

코드에 대한 설명을 작성해주세요.

💬고민사항 및 리뷰 요구사항

고민사항 및 의견 받고 싶은 부분 있으면 적어두기

비고 (Optional)

참고했던 링크 등 참고 사항을 적어주세요. 코드 리뷰하는 사람이 참고해야 하는 내용을 자유로운 형식으로 적을 수 있습니다.

Summary by CodeRabbit

개선 사항

  • 신규 기능

    • 상세 조회 시 모든 하위 섹션을 함께 불러오는 조회 방식이 추가되었습니다.
  • 리팩토링

    • 상세 조회 로직이 개선되어 하위 섹션 로딩과 권한 검증이 분리되어 더 명확해졌습니다.
    • 데이터 로딩 효율성이 최적화되었습니다.
  • 테스트

    • 관련 단위 테스트가 새로운 조회 방식에 맞춰 업데이트되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

- 서브섹션 한번에 조회하도록 수정
@2ghrms 2ghrms self-assigned this Dec 3, 2025
@2ghrms 2ghrms added the 🧵 REFACTOR 코드 리팩토링 label Dec 3, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • 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

Walkthrough

사업계획서와 모든 하위 섹션을 단일 조회로 불러오는 메서드가 추가되고, 서비스에서 로드와 소유권 검증이 분리되었습니다. 리포지토리는 LEFT JOIN FETCH 쿼리를 제공하며 관련 인터페이스와 JPA 어댑터, 테스트들이 해당 변경을 반영합니다.

Changes

Cohort / File(s) Summary
쿼리 인터페이스
src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java
getOrThrowWithAllSubSections(Long id) 메서드 추가: 모든 서브섹션을 포함한 사업계획서 조회 계약 추가
영속성 레이어 — 리포지토리
src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanRepository.java
findByIdWithAllSubSections(@param("id") Long id) 메서드 추가: 다중 LEFT JOIN FETCH JPQL로 사업계획서 및 모든 연관 서브엔티티를 한 번에 로드
영속성 레이어 — JPA 어댑터
src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java
getOrThrowWithAllSubSections(Long id) 메서드 추가: 리포지토리 호출 후 미존재 시 BusinessPlanException(BUSINESS_PLAN_NOT_FOUND) 발생
애플리케이션 서비스
src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java
getBusinessPlanDetail() 변경: 기존의 소유권 검사 결합 로직을 제거하고 getOrThrowWithAllSubSections()로 로드한 뒤 plan.isOwnedBy(memberId)로 별도 검증 수행
단위 테스트
src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java
테스트에서 getOrThrow(...) 호출을 getOrThrowWithAllSubSections(...)로 대체: 전체 서브섹션 반환 경로에 맞춘 목 설정 변경

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 검토 포인트:
    • findByIdWithAllSubSections JPQL이 모든 예상 연관관계를 정확히 fetch 하는지(중복/누락/카르티션 곱 여부) 검증
    • JPA 쿼리 성능 및 쿼리 결과가 엔티티 그래프와 매핑되는지 확인
    • 서비스에서 소유권 검증 분리로 인한 권한 예외 처리 흐름(동일한 접근 경로에 누락된 검사 없는지) 점검
    • 단위 테스트가 변경된 호출을 적절히 목킹하고 있으며 실제 시나리오를 충분히 커버하는지 확인

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 '쿼리 개선'으로 PR의 핵심 변화인 서브섹션 조회 쿼리 최적화(단일 쿼리로 통합)를 정확하게 반영하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SRLT-97-QA-사항-반영

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.

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Test Results

252 tests   252 ✅  10s ⏱️
 48 suites    0 💤
 48 files      0 ❌

Results for commit c3df1b7.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java (1)

75-78: 상세 조회에서만 전체 서브섹션 로드하도록 분리한 점 좋습니다

  • getBusinessPlanDetail에서만 getOrThrowWithAllSubSections를 써서, 무거운 fetch 그래프를 필요한 곳에만 제한한 점이 도메인/성능 관점에서 깔끔합니다.
  • 이후 plan.isOwnedBy(memberId) 체크로 기존 getOwnedBusinessPlanOrThrow와 동일한 예외 전략(존재하지 않으면 BUSINESS_PLAN_NOT_FOUND, 소유 아니면 UNAUTHORIZED_ACCESS)도 유지되고 있습니다.

추가로, 중복되는 소유권 체크를 줄이고 싶다면 아래와 같이 별도 헬퍼로 빼는 것도 선택지입니다 (필수는 아닙니다):

private BusinessPlan getOwnedBusinessPlanWithAllSubSectionsOrThrow(Long planId, Long memberId) {
    BusinessPlan plan = businessPlanQuery.getOrThrowWithAllSubSections(planId);
    if (!plan.isOwnedBy(memberId)) {
        throw new BusinessPlanException(BusinessPlanErrorType.UNAUTHORIZED_ACCESS);
    }
    return plan;
}

그 후 getBusinessPlanDetail에서 이 메서드를 사용하는 식으로 공통화할 수 있습니다.

src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanRepository.java (1)

26-47: 단일 쿼리로 전체 섹션/서브섹션을 가져오는 설계는 의도에 잘 부합합니다 (JPA 매핑만 한 번 확인 권장)

  • SELECT DISTINCT bp + LEFT JOIN FETCH 조합으로 루트 BusinessPlan 한 건과 연관된 섹션/서브섹션을 한 번에 로딩하는 구조가, 이번 PR 목적(서브섹션 N+1 제거)에 잘 맞습니다.
  • findById@EntityGraph는 상위 섹션까지만 로딩하고, 이 메서드는 더 깊은 서브엔티티까지 가져오는 역할로 자연스럽게 분리된 것도 좋습니다.

다만 JPA/Hibernate 특성상 한 쿼리에서 여러 개의 컬렉션(@onetomany, 특히 List/Bag)JOIN FETCH하면 “multiple bag fetch” 예외가 발생할 수 있습니다. 지금 쿼리에 등장하는 필드들 중에서:

  • tc.teamMembers 외에 overviewBasic, problemBackground, problemPurpose, problemMarket, feasibilityStrategy, feasibilityMarket, growthModel, growthFunding, growthEntry 등이 컬렉션(특히 List) 로 매핑되어 있다면 문제가 될 수 있습니다.
  • 전부 @OneToOne / @ManyToOne 또는 Set 기반이라면 현재 설계는 문제 없습니다.

한 번 엔티티 매핑을 보시고, 만약 두 개 이상이 @OneToMany List라면:

  • 일부 연관을 @BatchSize + 지연 로딩으로 빼거나,
  • DTO 전용 쿼리로 분리하는 등의 전략을 고려해 보시면 좋겠습니다.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73c0aa1 and 35944b3.

📒 Files selected for processing (4)
  • src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java (1 hunks)
  • src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanRepository.java (1 hunks)
  • src/main/java/starlight/application/businessplan/BusinessPlanServiceImpl.java (1 hunks)
  • src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java (1 hunks)
⏰ 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). (2)
  • GitHub Check: test
  • GitHub Check: build-and-push
🔇 Additional comments (3)
src/main/java/starlight/application/businessplan/required/BusinessPlanQuery.java (1)

11-12: 모든 서브섹션까지 로드하는 포트 메서드 네이밍/역할 적절해 보입니다

getOrThrowWithAllSubSections로 용도가 명확하게 드러나고, 기존 getOrThrow와 책임이 잘 분리된 것 같습니다. 후속 어댑터/서비스 구현과도 일관적이라 이대로 가져가도 무방해 보입니다.

src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanJpa.java (1)

25-30: 포트 구현이 기존 getOrThrow 패턴과 잘 맞습니다

findByIdWithAllSubSections -> BusinessPlanException(BUSINESS_PLAN_NOT_FOUND)로 매핑하는 로직이 기존 getOrThrow와 동일해 일관성이 좋습니다. 서비스단의 @Transactional(readOnly = true) 컨텍스트에서도 무리 없이 동작할 구조입니다.

src/main/java/starlight/adapter/businessplan/persistence/BusinessPlanRepository.java (1)

19-23: 미리보기 쿼리 가독성 개선 👍

텍스트 블록으로 정렬/정렬 기준이 한눈에 들어와서, COALESCE(modifiedAt, createdAt) DESC, id DESC 정렬 의도가 더 명확합니다. 동작 변경 없이 리팩터링된 것으로 보입니다.

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 (1)
src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java (1)

329-362: 쿼리 최적화를 위한 테스트 변경이 완료되었습니다.

getBusinessPlanDetail 메서드와 관련된 두 테스트 모두 새로운 getOrThrowWithAllSubSections 메서드를 사용하도록 올바르게 업데이트되었습니다.

참고로 다른 테스트들(예: upsertSubSection, deleteSubSection 등)은 여전히 getOrThrow를 사용하는데, 이는 올바른 선택입니다. 해당 메서드들은 개별 서브섹션만 필요로 하므로 lazy loading이 더 효율적입니다.

선택사항: 통합 테스트에서 실제 쿼리 성능 검증 고려

단위 테스트는 mock을 사용하므로 실제 쿼리 최적화 효과를 검증하지 못합니다. 여유가 되신다면 통합 테스트나 쿼리 실행 계획 검증을 통해 N+1 문제가 실제로 해결되었는지 확인하시면 좋을 것 같습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35944b3 and c3df1b7.

📒 Files selected for processing (1)
  • src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java (2 hunks)
⏰ 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). (2)
  • GitHub Check: test
  • GitHub Check: build-and-push
🔇 Additional comments (2)
src/test/java/starlight/application/businessplan/BusinessPlanServiceImplUnitTest.java (2)

340-340: 쿼리 최적화 변경이 올바르게 반영되었습니다.

getOrThrowWithAllSubSections를 사용하여 모든 서브섹션을 한 번에 조회하도록 mock을 설정한 것이 적절합니다. 이는 N+1 쿼리 문제를 해결하기 위한 PR 목표와 일치합니다.


358-358: 권한 검증 테스트도 새로운 조회 메서드와 일관성 있게 업데이트되었습니다.

getBusinessPlanDetail 메서드가 사용하는 조회 방식 변경에 맞춰 테스트 mock도 일관성 있게 수정되었습니다. 권한 검증 로직은 조회 방식과 무관하게 올바르게 동작합니다.

@2ghrms 2ghrms merged commit 595112c into develop Dec 28, 2025
4 checks passed
@2ghrms 2ghrms deleted the SRLT-97-QA-사항-반영 branch December 28, 2025 16:36
@SeongHo5356 SeongHo5356 added the ☁️ DEPLOY 배포 관련 label Jan 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

☁️ DEPLOY 배포 관련 🧵 REFACTOR 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants