Skip to content

[FEAT] 사용자 프로필 조회 및 관리 API 구현#47

Open
p1001q wants to merge 18 commits into
developfrom
feat/#39-user-profile-api
Open

[FEAT] 사용자 프로필 조회 및 관리 API 구현#47
p1001q wants to merge 18 commits into
developfrom
feat/#39-user-profile-api

Conversation

@p1001q

@p1001q p1001q commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📍 개요

사용자 프로필 조회/최초 등록(온보딩)/수정 API 3개를 구현합니다.

⛓️‍💥 관련 이슈


🛠️ 작업 내용

  • GET /api/users/me/profile 프로필 조회 API 구현 (닉네임/프로필이미지/악기/숙련도/구독등급/누적 통계)
  • POST /api/users/me/profile 프로필 최초 등록(온보딩) API 구현
    — User/Student/StudentInstrument/Subscription 생성을 하나의 트랜잭션으로 처리
  • PATCH /api/users/me/profile 프로필 수정 API 구현 (닉네임/화성학 숙련도)
  • User/Student/StudentInstrument/Instrument/Subscription/UserStatistics Repository 6개 신규 구현
  • UserErrorStatus/StudentErrorStatus에 신규 에러 코드 4개 추가
    (USER_404_01, USER_409_01, USER_409_02, STUDENT_404_01)
  • 서버 기동 시 PIANO 악기 시드 데이터를 자동 등록하는 InstrumentSeeder 추가

🔥 리뷰 요청 사항

리뷰어가 중점적으로 확인해주었으면 하는 내용을 작성해주세요.

  • 프로필 조회 응답의 completedLearningCount는 동균 강님의 feat/#32-learning-status-api 병합 전까지 0으로 고정해뒀습니다(TODO 주석 남김). 병합 후 실제 집계 쿼리로 교체 예정인데, 이 방식으로 우선 진행해도 괜찮을지 확인 부탁드려요.
  • 인증된 사용자 식별은 새로 구현한 게 아니라 기존 SecurityUtil.getCurrentUserId()를 그대로 재사용했습니다.

✅ 체크리스트

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

📎 참고 사항

  • S3 기본 프로필 이미지(버킷/설정값)는 준비돼 있으나 (aws 버킷 파서 넣어놨어요)
    소셜 로그인/회원가입 시 등록되는 값이기에 아직 미구현으로
    유저에게 기본 이미지가 실제로 적용되는지는 이번 PR 범위에서 검증하지 못했습니다.
  • 프로필 최초 등록 API는 온보딩 전역 차단 필터의 화이트리스트에 포함되어야 합니다.
    해당 필터 아직 구현 전이라 지금은 별도 조치가 없습니다. 필터 구현 시 조율 필요합니다.
  • UserRepository/UserErrorStatusUSER_NOT_FOUND
    동균님의 미병합 브랜치(feat/#32-learning-status-api, PR [FEAT] 학습 도메인 api 기본 구현 #46)에도 동일하게 추가되어 있어서 그쪽이 먼저 머지되면 병합 시 사소한 정리가 필요할 수 있습니다.
  • 테스트는 일단 PR 올리고 추후 진행할 예정입니다 > 했어용

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능
    • /api/users/me/profile에서 내 프로필 조회/등록/수정을 추가했습니다.
    • 온보딩에서 닉네임, 실력, 구독 티어(FREE/ STANDARD/ PRO)를 설정할 수 있습니다.
    • 프로필 조회 시 대표 악기와 연습 관련 통계를 함께 제공합니다.
    • 애플리케이션 시작 시 피아노 악기 정보가 자동 준비됩니다.
  • 오류 개선
    • 사용자/학생/온보딩/닉네임 중복 관련 오류 처리 범위를 확장했습니다.
    • 대표 악기, 활성 구독, 악기 시드 미존재 등 추가 오류 응답을 반영했습니다.
  • 테스트
    • 프로필 API 동작 및 예외 응답을 검증하는 테스트를 추가했습니다.

@p1001q p1001q self-assigned this Jul 24, 2026
@p1001q p1001q added the ✨feat label Jul 24, 2026
@p1001q
p1001q marked this pull request as draft July 24, 2026 13:11
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@p1001q, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 89cc36ff-bd9a-4e26-8adc-b6be3c265ad6

📥 Commits

Reviewing files that changed from the base of the PR and between 825a92c and a61f637.

📒 Files selected for processing (5)
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/controller/UserProfileController.java
  • src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
  • src/main/java/com/mr/domain/user/dto/UserProfileResponseDTO.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java
📝 Walkthrough

Walkthrough

사용자 프로필 조회·등록·수정 API와 요청·응답 DTO가 추가되었습니다. 사용자·학생·악기·구독·통계 저장소와 오류 상태가 확장되었으며, 애플리케이션 시작 시 피아노 시딩과 컨트롤러 테스트가 추가되었습니다.

Changes

사용자 프로필 관리

Layer / File(s) Summary
프로필 계약과 저장소 기반
src/main/java/com/mr/domain/user/dto/*, src/main/java/com/mr/domain/subscriptions/..., src/main/java/com/mr/domain/statistics/..., src/main/java/com/mr/domain/user/repository/*
온보딩·수정 요청과 프로필·통계 응답 record가 정의되고, 구독 티어 및 사용자 관련 JPA 조회 인터페이스가 추가되었습니다.
프로필 서비스 흐름
src/main/java/com/mr/domain/user/service/UserProfileService.java, src/main/java/com/mr/domain/user/exception/*
현재 사용자 조회, 온보딩 중복 방지, 닉네임 중복 검증, 학생·대표 악기·구독 저장, 프로필 조회·수정 및 통계 응답 생성을 구현했습니다.
프로필 HTTP 엔드포인트와 검증
src/main/java/com/mr/domain/user/controller/UserProfileController.java, src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
GET, POST, PATCH /api/users/me/profile 엔드포인트와 성공 및 주요 오류 응답을 검증하는 MockMvc 테스트를 추가했습니다.
기본 악기 초기화
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
애플리케이션 시작 시 PIANO 악기가 없으면 생성해 저장합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UserProfileController
  participant UserProfileService
  participant Repositories
  Client->>UserProfileController: 프로필 API 요청
  UserProfileController->>UserProfileService: 요청 DTO 전달
  UserProfileService->>Repositories: 사용자·학생·악기·구독·통계 조회/저장
  Repositories-->>UserProfileService: 도메인 데이터 반환
  UserProfileService-->>UserProfileController: 응답 DTO 반환
  UserProfileController-->>Client: ApiResponse 반환
Loading

Possibly related PRs

  • Musereview/BE#7: UserStatisticsRepositoryUserStatistics 모델 사용이 직접 연결됩니다.
  • Musereview/BE#18: 사용자·학생·악기 도메인과 프로필 저장소 및 오류 상태 확장이 이어집니다.
  • Musereview/BE#38: 구독 오류 상태와 온보딩 구독 처리의 코드 레벨 연결이 있습니다.

Poem

피아노 씨앗 싹을 틔우고
프로필 문이 열렸네
닉네임은 겹치지 않게
구독은 날짜 따라 반짝
온보딩, 멋진 시작 🎵

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 이슈 #39의 핵심 기능은 반영됐지만, 최초 등록 API를 온보딩 차단 필터 화이트리스트에 포함한 근거가 보이지 않습니다. 온보딩 차단 필터 화이트리스트에 POST /api/users/me/profile를 추가하고, 관련 설정과 테스트를 함께 보강하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 사용자 프로필 조회·등록·수정 API 추가라는 핵심 변경을 정확히 요약합니다.
Out of Scope Changes check ✅ Passed 추가된 변경은 프로필 API, 시드, 저장소, 예외 처리와 직접 연결되어 있어 명확한 범위 이탈은 보이지 않습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#39-user-profile-api

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 marked this pull request as ready for review July 24, 2026 13:17

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/main/java/com/mr/domain/user/service/UserProfileService.java (1)

46-49: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

미결정 예외는 GeneralException + BaseCode 패턴으로 전환해 주세요.

getUser, getStudent, ensureNicknameNotTaken는 잘못된 요청 상태를 ERROR_XXX 코드와 HTTP 상태 기반으로 처리하므로, 대표 악기/구독 정보/PIANO 시드 미존재 역시 동일하게 처리해 주세요. IllegalStateException은 전역 Exception 예외 핸들러로 내려가 일관되지 않은 500 응답이 됩니다. UserErrorStatus 또는 StudentErrorStatus에 각 사례에 맞는 신규 코드를 추가하고 getMyProfile()/onboarding 경로의 orElseThrow에서 new GeneralException(...)로 연결해 주세요. (예: 대표 악기 미존재 → STUDENT_NOT_FOUND를 재사용하거나 별도 코드를 추가하는 것이 적절합니다.)

🤖 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/main/java/com/mr/domain/user/service/UserProfileService.java` around
lines 46 - 49, Replace the IllegalStateException fallbacks in
UserProfileService.getMyProfile() and the onboarding path at UserProfileService
lines 46-49, 51-53, and 79-80 with GeneralException using the appropriate
UserErrorStatus or StudentErrorStatus BaseCode. Add dedicated error codes where
needed, or reuse STUDENT_NOT_FOUND for missing representative instrument,
subscription, and PIANO seed cases, while preserving each existing orElseThrow
condition.
🤖 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.

Inline comments:
In
`@src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java`:
- Line 10: Update SubscriptionRepository.findFirstByUserOrderByStartDateDesc to
filter for currently active subscriptions according to Subscription.isActive(),
rather than returning expired history; then update
UserProfileService.getMyProfile() to call the new active-subscription query
while preserving the existing latest-start-date ordering.

In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 23-24: Update InstrumentSeeder’s PIANO seeding flow to be safe
across concurrent application instances: enforce a database-level unique
constraint on Instrument.code and use an idempotent duplicate-key-ignore or
database-native upsert approach instead of the current findByCode/orElseGet
check-then-act pattern. Preserve successful retrieval or creation of the single
PIANO instrument.

In `@src/main/java/com/mr/domain/user/repository/UserRepository.java`:
- Around line 6-10: Merge the duplicate UserRepository definitions before
integration by removing the empty counterpart and retaining a single repository
interface with the existsByNicknameAndUserIdNot method. Ensure the resulting
UserRepository keeps the JpaRepository<User, Long> contract and avoids duplicate
FQCN definitions.

In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 74-75: In UserProfileService, update both registerProfile (lines
74-75) and updateProfile (lines 104-106) to validate nickname availability with
ensureNicknameNotTaken before calling user.updateNickname(request.nickname()).
Preserve the existing NICKNAME_DUPLICATED business exception behavior and apply
the same ordering at both sites.
- Around line 65-96: Update registerProfile to finalize onboarding only after
Student, primary StudentInstrument, and Subscription creation succeeds: if
onboarding state is stored as a User flag, call user.completeOnboarding() before
the transaction completes; otherwise check for an existing Student by user and
throw ONBOARDING_ALREADY_COMPLETED before creating duplicates. Preserve the
existing initial onboarding guard and ensure the successful path synchronizes
the User onboarding state with the Student relationship.

---

Nitpick comments:
In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 46-49: Replace the IllegalStateException fallbacks in
UserProfileService.getMyProfile() and the onboarding path at UserProfileService
lines 46-49, 51-53, and 79-80 with GeneralException using the appropriate
UserErrorStatus or StudentErrorStatus BaseCode. Add dedicated error codes where
needed, or reuse STUDENT_NOT_FOUND for missing representative instrument,
subscription, and PIANO seed cases, while preserving each existing orElseThrow
condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 05a8619f-ae4d-440c-ae53-a2425110257c

📥 Commits

Reviewing files that changed from the base of the PR and between 274468e and 5d3a2c1.

📒 Files selected for processing (14)
  • src/main/java/com/mr/domain/statistics/repository/UserStatisticsRepository.java
  • src/main/java/com/mr/domain/subscriptions/entity/enums/SubscriptionTier.java
  • src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/controller/UserProfileController.java
  • src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
  • src/main/java/com/mr/domain/user/dto/UserProfileResponseDTO.java
  • src/main/java/com/mr/domain/user/exception/StudentErrorStatus.java
  • src/main/java/com/mr/domain/user/exception/UserErrorStatus.java
  • src/main/java/com/mr/domain/user/repository/InstrumentRepository.java
  • src/main/java/com/mr/domain/user/repository/StudentInstrumentRepository.java
  • src/main/java/com/mr/domain/user/repository/StudentRepository.java
  • src/main/java/com/mr/domain/user/repository/UserRepository.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java

Comment thread src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java Outdated
Comment thread src/main/java/com/mr/domain/user/config/InstrumentSeeder.java Outdated
Comment thread src/main/java/com/mr/domain/user/repository/UserRepository.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated
Comment thread src/main/java/com/mr/domain/user/repository/UserRepository.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated

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

Actionable comments posted: 1

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

Inline comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 26-36: InstrumentSeeder의 중복 시드 처리에서
DataIntegrityViolationException을 save 호출 내부에만 의존하지 말고, 실제 DB flush/commit 경계에서
예외가 발생하도록 트랜잭션 경계를 분리하세요. 시드 저장 작업을 별도 트랜잭션 메서드로 이동하거나 호출해 saveAndFlush()로 즉시
반영하고, 바깥 흐름에서 해당 예외를 잡아 기존처럼 경쟁 인스턴스의 중복 삽입을 무시하도록 수정하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 57c7a686-182b-4504-9482-aa822af6daf2

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3a2c1 and 8403213.

📒 Files selected for processing (4)
  • src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java
  • src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/mr/domain/user/service/UserProfileService.java

Comment thread src/main/java/com/mr/domain/user/config/InstrumentSeeder.java

import com.mr.domain.user.entity.enums.TheoryLevel;

public class UserProfileRequestDTO {

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.

P1: DTO 필드에 유효성 검증 어노테이션 누락
UserErrorStatus에 정의된 닉네임 형식 규칙 등에 맞춰 @notblank, @pattern, @NotNull 등의 검증 어노테이션을 추가하면 좋을 것 같아요~

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

의도적으로 DTO에 검증 어노테이션을 넣지 않았습니다.
닉네임/숙련도 형식 오류는 명세서 기준으로 USER_400_01/USER_400_02/STUDENT_400_02처럼 도메인별 코드로 응답해야 하는데, @Valid를 걸면 MethodArgumentNotValidException이 먼저 잡혀서 일괄 COMMON_400_01로만 응답이 나가 명세와 어긋나게 됩니다. 이미 User.updateNickname()/Student.create() 엔티티 자체에서 같은 검증(형식/필수값)을 하고 있어서, DTO에 중복으로 검증을 걸면 오히려 같은 종류의 오류가 케이스에 따라 COMMON_400_01과 도메인 코드로 서로 다르게 나가는 일관성 문제가 생길 수 있어 이번엔 반영하지 않았습니다...!

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<UserProfileResponseDTO.OnboardingResponse> registerProfile(
@RequestBody UserProfileRequestDTO.OnboardingRequest request

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.

P1: DTO에 검증 로직을 추가할 경우 @RequestBody 뒤에 @Valid 어노테이션도 추가해야 합니다~
@Valid 어노테이션이 없을 경우에는 DTO에 추가한 검증 로직이 동작하지 않을 수 있어요! 서비스 로직 진입 전에 400 에러로 걸러냅시다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

의도적으로 DTO에 검증 어노테이션을 넣지 않았습니다.
닉네임/숙련도 형식 오류는 명세서 기준으로 USER_400_01/USER_400_02/STUDENT_400_02처럼 도메인별 코드로 응답해야 하는데, @Valid를 걸면 MethodArgumentNotValidException이 먼저 잡혀서 일괄 COMMON_400_01로만 응답이 나가 명세와 어긋나게 됩니다. 이미 User.updateNickname()/Student.create() 엔티티 자체에서 같은 검증(형식/필수값)을 하고 있어서, DTO에 중복으로 검증을 걸면 오히려 같은 종류의 오류가 케이스에 따라 COMMON_400_01과 도메인 코드로 서로 다르게 나가는 일관성 문제가 생길 수 있어 이번엔 반영하지 않았습니다...!

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

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java (1)

32-38: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

중복 키 외의 무결성 오류는 그대로 전파해 주세요.

DataIntegrityViolationException은 unique 충돌뿐 아니라 Hibernate에서 감싸는 무결성 오류 일반을 포함할 수 있습니다. 현재는 Instrumentcode unique 제약 전용 예외까지 “이미 생성됨”으로 삼키기 때문에, 다른 저장 소스 무결성 사고가 발생하면 애플리케이션은 정상 기동되지만 온보딩에서 INSTRUMENT_NOT_SEEDED가 발생합니다. 중복(unique) 제약 원인일 때만 무시하고, 그 외 무결성 오류는 다시 던지도록 수비적인 isDuplicateKeyViolation(e) 판별 패턴을 함께 사용하세요. PostgreSQL의 unique violation은 SQLState == "23505" 기준으로 필터링할 수 있고, Spring Framework의 exception translation 참고도 도움이 됩니다.

🤖 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/main/java/com/mr/domain/user/config/InstrumentSeeder.java` around lines
32 - 38, Update the exception handling around InstrumentSeeder’s
instrumentRepository.saveAndFlush call to ignore only duplicate-key violations
identified by an isDuplicateKeyViolation(e) predicate, including PostgreSQL
SQLState "23505". Re-throw DataIntegrityViolationException for all other
integrity failures, while preserving the existing informational log for genuine
duplicate seed attempts.
src/main/java/com/mr/domain/user/service/UserProfileService.java (1)

86-92: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

온보딩 중복 제약은 flush 시점에 감지해야 합니다.

StudentRepositoryJpaRepository를 구현하고, Student.user_id@JoinColumn(..., unique = true)로 지정되어 있습니다. save()는 persistence context에 저장 상태를 등록하지만 INSERT 실행은 대부분 commit 직전 flush 시점에 수행되므로, 동시 요청으로 인한 DataIntegrityViolationExceptiontry 외부에서 발생할 수 있습니다. 이때 409 변환이 아니라 일반 서버 오류로 처리됩니다.

save() 대신 saveAndFlush()를 사용해 주석을 유지하면서 constraint violation을 해당 블록 내부로 당겨오세요. 추가로 동시 온보딩 요청에 대한 테스트도 함께 추가하면 안전합니다. 관련 개념: Spring Data JPA flush/commit, Hibernate write-behind flushing.

수정 예시
-            student = studentRepository.save(Student.create(user, request.skillLevel()));
+            student = studentRepository.saveAndFlush(
+                    Student.create(user, request.skillLevel()));
🤖 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/main/java/com/mr/domain/user/service/UserProfileService.java` around
lines 86 - 92, Update the onboarding persistence in UserProfileService to use
StudentRepository.saveAndFlush() instead of save() within the existing
try/catch, ensuring the unique-constraint DataIntegrityViolationException is
raised and translated to ONBOARDING_ALREADY_COMPLETED inside that block. Add
coverage for concurrent onboarding requests if the existing test structure
supports it.
🧹 Nitpick comments (1)
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java (1)

22-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

주석을 실제 호출인 saveAndFlush()에 맞춰 주세요.

현재 구현은 saveAndFlush()를 사용하지만 주석은 save()의 독립 트랜잭션을 설명하고 있어, 이후 트랜잭션 동작을 잘못 이해하게 만들 수 있습니다. Spring Data JPA의 실제 호출 경계에 맞게 주석을 갱신해 주세요.

🤖 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/main/java/com/mr/domain/user/config/InstrumentSeeder.java` around lines
22 - 25, Update the transaction-behavior comment in InstrumentSeeder.run() to
refer to the actual saveAndFlush() calls rather than save(), while preserving
its explanation that run() is not transactional and each persistence call
handles duplicate failures independently.
🤖 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.

Outside diff comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 32-38: Update the exception handling around InstrumentSeeder’s
instrumentRepository.saveAndFlush call to ignore only duplicate-key violations
identified by an isDuplicateKeyViolation(e) predicate, including PostgreSQL
SQLState "23505". Re-throw DataIntegrityViolationException for all other
integrity failures, while preserving the existing informational log for genuine
duplicate seed attempts.

In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 86-92: Update the onboarding persistence in UserProfileService to
use StudentRepository.saveAndFlush() instead of save() within the existing
try/catch, ensuring the unique-constraint DataIntegrityViolationException is
raised and translated to ONBOARDING_ALREADY_COMPLETED inside that block. Add
coverage for concurrent onboarding requests if the existing test structure
supports it.

---

Nitpick comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 22-25: Update the transaction-behavior comment in
InstrumentSeeder.run() to refer to the actual saveAndFlush() calls rather than
save(), while preserving its explanation that run() is not transactional and
each persistence call handles duplicate failures independently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fa2941a-9a7e-40eb-89fc-c979824693e0

📥 Commits

Reviewing files that changed from the base of the PR and between 8403213 and 825a92c.

📒 Files selected for processing (7)
  • src/main/java/com/mr/domain/subscriptions/exception/SubscriptionErrorStatus.java
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
  • src/main/java/com/mr/domain/user/exception/InstrumentErrorStatus.java
  • src/main/java/com/mr/domain/user/exception/StudentInstrumentErrorStatus.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java
  • src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
  • src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java


Student student;
try {
student = studentRepository.save(Student.create(user, request.skillLevel()));

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.

P2: 동시 온보딩 요청에 대한 DataIntegrityViolationException을 studentRepository.save() 주변에서 처리하고 있는데, JPA에서는 실제 INSERT나 UNIQUE 제약 검증이 flush 또는 트랜잭션 커밋 시점에 발생할 수 있어 현재 catch 문에서 잡히지 않을 가능성이 있어요!

이럴 경우에 중복 요청이 의도한 ONBOARDING_ALREADY_COMPLETED 409가 아니라 500으로 응답될 수 있습니당.

→ saveAndFlush()를 사용해 이 위치에서 예외를 발생시키거나, DB 무결성 예외를 전역에서 도메인 예외로 변환하는 방안도 한 번 고려해 주세요!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Student도 Instrument처럼 GenerationType.IDENTITY라 save() 시점에 이미 즉시 INSERT가 나가긴 합니다!
명시적으로 saveAndFlush()로 바꿔서 의도를 분명히 했습니다!

.orElseThrow(() -> new GeneralException(InstrumentErrorStatus.INSTRUMENT_NOT_SEEDED));
studentInstrumentRepository.save(StudentInstrument.createPrimary(student, piano));

String tier = validateSubscriptionTier(request.subscriptionTier());

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.

P2: subscriptionTier 검증이 Student와 StudentInstrument 생성 이후에 수행되고 있어, 잘못된 등급이 전달될 경우에도 여러 저장 로직을 거친 뒤 예외가 발생하는 것 같습니다.
트랜잭션 롤백으로 데이터는 남지 않겠지만, fail-fast 관점에서 구독 등급 검증을 메서드 초반으로 이동하면 좋을 것 같아요~

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

피드백 감사합니다!
fail-fast 관점에서 subscriptionTier 검증을 온보딩 완료 체크 바로 다음, 닉네임/Student 생성보다 앞으로 옮겼습니다.

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

코멘트 확인 부탁드립니다~

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.

✨ Feature - 사용자 프로필 조회 및 관리 API 구현

3 participants