Skip to content

[FIX] 코드 예시 noteNumbers 불변 리스트로 반환 - #202

Merged
p1001q merged 6 commits into
developfrom
fix/#201-chord-note-numbers-immutable
Aug 12, 2026
Merged

[FIX] 코드 예시 noteNumbers 불변 리스트로 반환#202
p1001q merged 6 commits into
developfrom
fix/#201-chord-note-numbers-immutable

Conversation

@p1001q

@p1001q p1001q commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

📍 개요

학습 단계 상세 조회 API에서 코드 예시(ChordExample)의 noteNumbers가 Hibernate가 관리하는 가변 컬렉션 그대로 노출되어 서버 에러가 발생하던 문제 수정

⛓️‍💥 관련 이슈


🛠️ 작업 내용

  • ChordExampleItem.from()에서 noteNumbersList.copyOf()로 감싸 불변 스냅샷으로 반환하도록 수정
  • 실제 로컬 DB에 저장 → 영속성 컨텍스트를 비우고 재조회해서 진짜 Hibernate 컬렉션(PersistentBag)을 태우는 통합 테스트 추가

🔥 리뷰 요청 사항

PM님이 리뷰에서 지적해주신 크래시 건입니다. 상황 공유드립니다.

  • noteNumbers@ElementCollection이라 운영 환경에서는 Hibernate가 관리하는 컬렉션 타입으로 로드되는데, 이걸 DTO에 그대로 담아 반환하다가 에러가 났던 것으로 보입니다.
  • 처음엔 기존 Mockito 목업 테스트(LearningServiceTest)에 assertion만 추가했는데, mock(ChordExample.class)List.of(...)를 반환하도록 스텁되어 있어서 수정 전/후 구분 없이 항상 통과하는 걸 확인했습니다(즉 이 크래시를 재현 못 하는 테스트).
  • 그래서 ChordExampleNoteNumbersIntegrationTest를 새로 추가해서, 실제 로컬 DB에 저장 후 entityManager.flush()+clear()로 영속성 컨텍스트를 비우고 다시 조회해 진짜 Hibernate 컬렉션을 태워봤습니다. 수정 전 코드로는 이 테스트가 실제로 실패(noteNumbers.add(1)이 예외 없이 성공)하고, 수정 후엔 통과하는 것까지 확인했습니다.
  • noteNumbers() 반환 타입 자체(List<Integer>)는 안 바뀌어서 API 응답 스펙에는 영향 없습니다.

✅ 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 코드 및 import를 제거했습니다.
  • 예외 처리를 적용했습니다.
  • 테스트를 완료했습니다.
  • 관련 Issue를 연결했습니다.

📎 참고 사항

  • 목업 테스트는 이런 종류의 버그(Hibernate 컬렉션 관련)를 못 잡을 수 있다는 걸 확인한 케이스라, 비슷한 @ElementCollection/@OneToMany 반환하는 다른 DTO도 한 번씩 점검해볼 만합니다.

🔎 크래시 원인 추가 확인 (디스코드 논의 정리)

  • 최근 open-in-viewfalse로 변경됐습니다 (DB 커넥션 과다 점유로 서버 다운되던 문제 대응 차 원정님이 의도적으로 설정)
  • 이 설정 때문에 Hibernate 세션이 트랜잭션(서비스 메서드) 종료와 동시에 닫히는데, ChordExample.noteNumbers(@ElementCollection(fetch = LAZY))는 기존 코드에서 실제 값을 안 읽고 Hibernate 프록시 참조만 DTO에 담아 넘기고 있었습니다
  • 그래서 컨트롤러가 응답을 직렬화(Jackson)하는 시점엔 세션이 이미 닫혀있어 크래시가 발생했던 것으로 확인됐습니다 (승기님이 서버 로그에서 확인)
  • 이번 PR의 List.copyOf(chordExample.getNoteNumbers()) 수정은 세션이 아직 열려있는 트랜잭션 내부에서 값을 즉시 읽어 복사해두기 때문에, 세션이 닫힌 뒤에도 안전하게 직렬화됩니다

open-in-view=false가 전역 설정이라, 다른 도메인에도 @OneToMany/@ElementCollection을 DTO에 그대로 노출하는 곳이 있으면 같은 패턴으로 터질 수 있어서 한 번씩 점검해봐도 좋을 것 같습니다.

🙋 쉽게 설명하면

  • noteNumbers는 원래 "DB 가면 있음"이라는 꼬리표만 붙어있던 값이었어요 (필요할 때만 진짜로 가져오는 방식)
  • 원래는 응답 다 만들 때까지 DB 연결이 열려있어서 나중에 그 값을 가져와도 문제없었는데
  • 서버 다운 문제 때문에 DB 연결을 훨씬 일찍 끊어버리는 설정으로 바뀌면서, 응답 만들기 직전에 "어 진짜 값 가져와야 되는데 연결 이미 끊겼네?" 하고 터진 거예요
  • 그래서 이번 수정은 연결 끊기기 전에 미리 진짜 값을 챙겨두는 코드(List.copyOf)로 바꾼 거고, 그게 크래시 원인이랑 정확히 맞아떨어졌습니다!

🐛 CI 간헐적 실패 조사 결과 (develop 최신화 과정에서 별도로 발견)

이 PR을 develop에 맞춰 merge하는 과정에서 CI가 간헐적으로 실패해서 원인을 팠습니다. 이번 PR 내용(ChordExample)이랑은 무관한 별개 버그입니다.

  • develop에 이미 머지된 PR #190 (UTC 기준 시간 통일, 이슈 ⚙️ Setting - 날짜 및 시간 UTC 기준 통일 #83)에서 HomeService의 "오늘 날짜" 계산을 LocalDate.now()LocalDate.now(ZoneId.of("Asia/Seoul"))로 명시적으로 고쳤는데, HomeServiceTest.java의 테스트 3곳(getHome_noPracticeToday_countsFromYesterday 등)이 이 수정에서 빠져서 여전히 시간대 지정 없는 LocalDate.now()를 쓰고 있었습니다.
  • CI(GitHub Actions)는 UTC로 도는데, 한국 시간 새벽 0~9시엔 UTC 날짜가 KST보다 하루 늦어서, 이 3개 테스트가 계산한 "오늘/어제"가 실제 서비스 로직이 계산한 한국 기준 날짜랑 하루 어긋나 expected: 1, but was: 0으로 실패했습니다.
  • 처음엔 이 PR의 ChordExample 필드 초기화 커밋과 실패 타이밍이 겹쳐서 그게 원인인 줄 의심했는데, 코드를 대조해보니 무관했습니다(HomeServiceTestlearning 도메인 코드를 전혀 참조하지 않음). 오히려 그 커밋을 새벽 3시에 푸시해서 CI가 하필 위험한 시간대(UTC/KST 날짜가 어긋나는 구간)에 돈 게 우연히 겹친 것이었습니다.
  • HomeServiceTest.java의 누락된 3곳을 같은 파일의 다른 테스트들과 동일하게 Asia/Seoul 명시로 맞춰서 고쳤습니다 (수정 전 코드로는 로컬에서도 재현 확인, 수정 후 통과 확인).

팀 전체 참고: 같은 이유로 한국 새벽 0~~9시 사이에 푸시하면, 시간대 처리가 빠진 다른 코드도 지금 작업 내용과 무관하게 CI가 실패할 수 있습니다.
#193 머지된 상태라 같은 이유로 ci 문제는 안 생길 겁니다!

Summary by CodeRabbit

개선 사항

  • 코드 예시의 음 번호 목록을 안전하게 제공해 외부에서 임의로 수정할 수 없도록 안정성을 강화했습니다.
  • 음 번호가 없는 코드 예시도 빈 목록으로 일관되게 제공됩니다.

테스트

  • 학습 단계 상세 조회 시 음 번호와 수정 제한을 검증했습니다.
  • 한국 표준시 기준의 날짜 및 출석 계산을 검증하도록 테스트를 보완했습니다.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ce80cd85-8bcc-4571-8fcc-967a0f05794f

📥 Commits

Reviewing files that changed from the base of the PR and between 26175a4 and 1e733e7.

📒 Files selected for processing (1)
  • src/test/java/com/mr/domain/learning/service/ChordExampleNoteNumbersIntegrationTest.java

📝 Walkthrough

Walkthrough

ChordExamplenoteNumbers 기본값을 빈 리스트로 초기화합니다. ChordExampleItem.from은 불변 복사본을 DTO에 담습니다. Hibernate 통합 테스트가 재조회 후 동작을 검증하며, 홈 서비스 테스트는 Asia/Seoul 시간대를 사용합니다.

Changes

코드 예시 음 번호 반환

Layer / File(s) Summary
불변 noteNumbers 응답과 검증
src/main/java/com/mr/domain/learning/entity/ChordExample.java, src/main/java/com/mr/domain/learning/dto/res/LearningStepDetailResponseDTO.java, src/test/java/com/mr/domain/learning/service/LearningServiceTest.java, src/test/java/com/mr/domain/learning/service/ChordExampleNoteNumbersIntegrationTest.java
noteNumbers를 빈 ArrayList로 초기화합니다. ChordExampleItem.fromList.copyOf로 불변 복사본을 생성합니다. 테스트는 값과 UnsupportedOperationException 발생을 검증합니다. 통합 테스트는 Hibernate 컬렉션을 재조회한 뒤 결과를 검증합니다.

홈 서비스 테스트 시간대

Layer / File(s) Summary
Asia/Seoul 날짜 계산
src/test/java/com/mr/domain/home/service/HomeServiceTest.java
어제 날짜와 오늘 날짜를 사용하는 테스트가 시스템 기본 시간대 대신 Asia/Seoul 시간대를 사용합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • Musereview/BE#55: ChordExampleItem.from의 현재 변경 대상 구조를 도입했습니다.

Poem

Hibernate 목록을 복사해
불변 DTO로 담았네.
음 번호는 그대로 있고,
수정 요청은 예외가 되네.
테스트는 박자를 맞추네.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning HomeServiceTest의 Asia/Seoul 시간대 변경은 [#201]의 noteNumbers 수정 및 검증 범위와 무관합니다. 시간대 변경을 별도 PR로 분리하거나, 이 PR에 포함할 필요성을 명시하고 관련 요구사항을 추가하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 noteNumbers를 불변 리스트로 반환하는 핵심 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed List.copyOf() 적용과 실제 Hibernate 컬렉션을 검증하는 통합 테스트를 추가해 [#201]의 요구사항을 충족합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 fix/#201-chord-note-numbers-immutable

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.

@p1001q p1001q self-assigned this Aug 11, 2026
@Musereview Musereview deleted a comment from coderabbitai Bot Aug 11, 2026

@rkdehdrbs7885-oss rkdehdrbs7885-oss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/test/java/com/mr/domain/home/service/HomeServiceTest.java (1)

148-150: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

현재 시각을 고정해 자정 경계의 테스트 실패를 방지하세요.

테스트 픽스처와 HomeServiceLocalDate.now(ZoneId.of("Asia/Seoul"))를 각각 호출합니다. 테스트가 한국 시간 자정 전후에 실행되면 두 호출이 서로 다른 날짜를 반환할 수 있습니다. 이 경우 어제 출석 테스트와 주간·월간 연습 시간 테스트가 간헐적으로 실패할 수 있습니다.

HomeService에 고정 가능한 시간 제공자를 주입하고, 테스트에서 동일한 고정 시각을 사용하세요. Java java.time.Clock의 fixed time 패턴을 참고하면 됩니다.

Also applies to: 163-166, 180-182, 194-196, 215-215, 230-234, 249-253

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/mr/domain/home/service/HomeServiceTest.java` around lines
148 - 150, HomeService and HomeServiceTest independently call LocalDate.now,
causing midnight-boundary flakes. Inject a java.time.Clock into HomeService,
derive the Seoul date from that clock, and configure the tests—including the
referenced fixtures and assertions—to use the same fixed clock instant and
Asia/Seoul zone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/test/java/com/mr/domain/home/service/HomeServiceTest.java`:
- Around line 148-150: HomeService and HomeServiceTest independently call
LocalDate.now, causing midnight-boundary flakes. Inject a java.time.Clock into
HomeService, derive the Seoul date from that clock, and configure the
tests—including the referenced fixtures and assertions—to use the same fixed
clock instant and Asia/Seoul zone.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 558f1e9e-6792-4921-85c0-3506124eb4a9

📥 Commits

Reviewing files that changed from the base of the PR and between d659b41 and 26175a4.

📒 Files selected for processing (1)
  • src/test/java/com/mr/domain/home/service/HomeServiceTest.java

@p1001q
p1001q merged commit f401771 into develop Aug 12, 2026
2 checks passed
@p1001q
p1001q deleted the fix/#201-chord-note-numbers-immutable branch August 12, 2026 06:32
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.

🐛 BugFix - 코드 예시 조회 시 noteNumbers 가변 컬렉션 노출 문제 수정

3 participants