-
Notifications
You must be signed in to change notification settings - Fork 2
[BE] SISC1-88 [FEAT] 사용자 본인 베팅 조회 API 구현 #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "SISC1-88-BE-\uC0AC\uC6A9\uC790-\uBCA0\uD305-\uC774\uB825-\uC870\uD68C-API-\uAD6C\uD604"
Conversation
변경사항 분석Walkthrough사용자의 베팅 이력 조회 기능을 추가했습니다. 새로운 REST 엔드포인트( Changes
Sequence DiagramsequenceDiagram
actor User
participant Controller as BettingController
participant Service as BettingService
participant Repository as UserBetRepository
participant DB as Database
User->>Controller: GET /api/user-bets/history
Controller->>Service: getAllMyBets(userId)
Service->>Repository: findAllByUserIdOrderByRound_SettleAtDesc(userId)
Repository->>DB: Query with ORDER BY round.settleAt DESC
DB-->>Repository: List<UserBet>
Repository-->>Service: List<UserBet>
Service-->>Controller: List<UserBet>
Controller-->>User: ResponseEntity<List<UserBet>>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 검토 시 추가 주의 사항:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
|
There was a problem hiding this 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 (18)
backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java (3)
25-27: UUID 컬럼 명시로 이식성/명확성 강화PostgreSQL 사용 시 UUID 컬럼을 명시하면 스키마 가독성과 이식성이 좋아집니다.
- @Column(nullable = false) - private UUID userId; + @Column(name = "user_id", columnDefinition = "uuid", nullable = false) + private UUID userId;
35-36: NULL 제약 충돌 가능성: 필드 기본값/검증 보강 권장
stakePoints,betStatus는 DBnullable=false인데, 빌더로 생성 시 누락되면 제약 위반 위험이 있습니다. 서비스에서 반드시 채우거나 DTO/엔티티에 @NotNull/검증 또는 @PrePersist 기본값 설정을 추가해 주세요.Also applies to: 41-43
32-34: 불리언 필드 네이밍 일관성엔티티는
isFree, DTO는free(@JsonProperty("isFree"))로 혼재합니다. 매핑 혼동을 줄이려면 엔티티도free로 통일을 고려해 주세요. Lombok+Jackson 조합에서 어노테이션 자동 복사 동작이 변경된 점도 유의하세요. Based on learningsbackend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java (1)
16-16: N+1 완화: 라운드 패치 조인 추가 권장사용자 베팅 목록에서
round정보를 함께 사용한다면 N+1이 발생할 수 있습니다.@EntityGraph로 패치 조인을 권장합니다.- List<UserBet> findAllByUserIdOrderByRound_SettleAtDesc(UUID userId); + @EntityGraph(attributePaths = "round") + List<UserBet> findAllByUserIdOrderByRound_SettleAtDesc(UUID userId);또한 페이징이 필요하다면 다음 시그니처도 고려해 주세요:
// import org.springframework.data.domain.Page; Pageable @EntityGraph(attributePaths = "round") Page<UserBet> findAllByUserIdOrderByRound_SettleAtDesc(UUID userId, Pageable pageable);backend/src/main/java/org/sejongisc/backend/point/service/PointHistoryService.java (1)
41-50: 조회 메서드에 readOnly 적용으로 부하 절감읽기 전용 트랜잭션을 지정하면 불필요한 flush/락을 피할 수 있습니다.
- public PointHistoryResponse getPointLeaderboard(int period) { + @Transactional(readOnly = true) + public PointHistoryResponse getPointLeaderboard(int period) { ... - public PointHistoryResponse getPointHistoryListByUserId(UUID userId, PageRequest pageRequest) { + @Transactional(readOnly = true) + public PointHistoryResponse getPointHistoryListByUserId(UUID userId, PageRequest pageRequest) {Also applies to: 52-57
backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetRequest.java (1)
24-28: 조건부 검증 추가 권장: 무료/유료 베팅 규칙
free=true일 때는stakePoints를 비우거나 0 허용,free=false일 때는stakePoints>=10이 필수입니다. DTO에 조건부 검증을 추가해 주세요.예시:
import jakarta.validation.constraints.AssertTrue; @AssertTrue(message = "무료 베팅은 포인트를 비우거나 0이어야 하고, 유료 베팅은 10 이상이어야 합니다.") public boolean isStakePointsValid() { if (free) { return stakePoints == null || stakePoints == 0; } return stakePoints != null && stakePoints >= 10; }backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java (1)
48-79: 호출 순서 보장 검증(InOrder) 추가 제안의도대로 포인트 이력이 UserBet 저장 이전에 호출되는지 순서를 검증해 주세요. 미래 리팩터 시 회귀 방지에 유용합니다.
예시:
@@ - verify(pointHistoryRepository, times(1)).save(any(PointHistory.class)); - verify(userBetRepository, times(1)).save(any()); + InOrder inOrder = inOrder(pointHistoryRepository, userBetRepository); + inOrder.verify(pointHistoryRepository, times(1)).save(any(PointHistory.class)); + inOrder.verify(userBetRepository, times(1)).save(any());backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (4)
57-60: 읽기 전용 트랜잭션 및 N+1/지연로딩 대비조회 전용 메서드에 readOnly를 붙이고, 엔티티 직접 반환은 피하는 것이 안전합니다. 페치 조인 또는 DTO 매핑으로 N+1과 직렬화 시 지연로딩 문제를 예방하세요.
예시:
- public List<UserBet> getAllMyBets(UUID userId) { + @Transactional(readOnly = true) + public List<UserBet> getAllMyBets(UUID userId) { // TODO : 필요 시 필터링, 정렬, 검색 로직 추가 return userBetRepository.findAllByUserIdOrderByRound_SettleAtDesc(userId); }추가 권장:
- Repository에 페치 조인 쿼리 추가(ex: findAllWithRoundByUserIdOrderByRoundSettleAtDesc) 또는 JPA Projection/DTO 반환.
- Open-Entity-Manager-In-View를 끄는 환경에서도 동작하도록 DTO로 고정.
원하시면 DTO 스펙과 매퍼 코드도 제안드리겠습니다.
102-111: PointHistory 상관키(originId) 일관성/멱등성차감 시 originId로 roundId를, 환불 시 userBetId를 사용하면 상호 매칭/중복 방지에 불리합니다. 멱등성과 상관성 확보를 위해 키 전략을 통일하세요.
대안:
- userBet를 먼저 PENDING 상태로 저장 → userBetId로 차감 기록(REQUIRES_NEW) → 성공 시 ACTIVE로 갱신(실패 시 삭제/보상).
- 혹은 (userId, roundId)를 조합한 결정적 UUID(또는 유니크 인덱스)로 originId 고정 후 idempotent upsert 보장(예: (origin, originId, userId) 유니크).
비용/복잡도 대비 어떤 전략이 비즈니스에 맞는지 결정이 필요합니다. 원하시면 구체 구현안 드리겠습니다.
136-144: 포인트 사유(PointReason) 세분화 권장환불에도 PointReason.BETTING을 사용하면 분석/정산이 어렵습니다. 예: BETTING_DEBIT, BETTING_REFUND 등으로 구분 권장.
관련 enum/리포트/대시보드까지 영향 범위 확인 후 반영해 주세요.
132-134: 취소 경계 일관성 확인(lockAt 포함 차단 여부)현재 취소는 now.isAfter(lockAt)만 차단하여 lockAt 동일 시각에는 허용됩니다. 베팅과 동일하게 [openAt, lockAt) 정책을 원하시면 조건을 ‘!now.isBefore(lockAt)’로 조정하세요.
예시:
- if (LocalDateTime.now().isAfter(betRound.getLockAt())){ + if (!LocalDateTime.now().isBefore(betRound.getLockAt())){ throw new CustomException(ErrorCode.BET_ROUND_CLOSED); }backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java (5)
237-280: 정렬 보장 테스트 강화 제안정렬 기준이 round.settleAt DESC입니다. 서로 다른 settleAt을 가진 더미 데이터를 사용해 정렬이 기대대로인지 명시적으로 검증해 주세요.
예시:
- round1.settleAt = now+2h, round2.settleAt = now+1h
- 반환 리스트가 [bet(round1), bet(round2)] 순서인지 확인
324-347: 호출 순서 검증 추가(PointHistory → save(UserBet))베팅 성공 시 포인트 차감이 먼저 호출되는지 InOrder로 보강하면 회귀를 막을 수 있습니다.
예시:
InOrder inOrder = inOrder(pointHistoryService, userBetRepository); inOrder.verify(pointHistoryService).createPointHistory(any(), anyInt(), any(), any(), any()); inOrder.verify(userBetRepository).save(any(UserBet.class));
396-409: 락 시각 경계 테스트 추가 제안lockAt과 동일 시각의 베팅 허용/차단 정책을 테스트로 고정해 주세요. 서비스 로직 경계 수정 시 빠르게 탐지 가능합니다.
381-395: DB 유니크 제약 위반 매핑 테스트 추가userBetRepository.save에서 DataIntegrityViolationException 발생 시 BET_DUPLICATE로 매핑되는지 테스트를 추가해 주세요.
예시:
when(betRoundRepository.findById(roundId)).thenReturn(Optional.of(openRoundNow())); when(userBetRepository.existsByRoundAndUserId(any(), any())).thenReturn(false); when(userBetRepository.save(any())).thenThrow(new DataIntegrityViolationException("dup")); CustomException ex = assertThrows(CustomException.class, () -> bettingService.postUserBet(userId, paidReq(100))); assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.BET_DUPLICATE);필요하시면 전체 테스트 코드 스니펫 제공 가능합니다.
17-18: 사소한 정리: 사용하지 않는 import
UserRepositoryimport는 사용되지 않습니다. 정리하면 가독성에 도움이 됩니다.backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (2)
51-59: 생성 API는 201 Created + Location 헤더 권장리소스 생성 시 200 OK 대신 201과 Location을 반환하세요.
예시:
- public ResponseEntity<UserBet> postUserBet( + public ResponseEntity<UserBet> postUserBet( @AuthenticationPrincipal CustomUserDetails principal, @RequestBody @Valid UserBetRequest userBetRequest){ - UserBet userBet = bettingService.postUserBet(principal.getUserId(), userBetRequest); - - return ResponseEntity.ok(userBet); + UserBet userBet = bettingService.postUserBet(principal.getUserId(), userBetRequest); + return ResponseEntity + .created(java.net.URI.create("/api/user-bets/" + userBet.getUserBetId())) + .body(userBet); }
70-76: 엔티티 직접 반환 대신 응답 DTO + 페이징 권장
- JPA 엔티티를 그대로 노출하면 스키마/지연로딩/N+1/민감 정보 노출 리스크가 있습니다. 전용 DTO로 매핑하세요.
- 이력은 누적되므로 페이징 쿼리/응답이 바람직합니다.
권장:
- GET /user-bets?size=&page= 형태로 변경
- Repository에 Page + 컨트롤러는 Page DTO로 래핑
- Lombok + Jackson 사용 시 @Jacksonized/@JsonProperty 설정 확인 필요(최근 Lombok 동작 변경 참고). Based on learnings
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java(3 hunks)backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetRequest.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java(3 hunks)backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java(1 hunks)backend/src/main/java/org/sejongisc/backend/point/service/PointHistoryService.java(2 hunks)backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java(3 hunks)backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTransactionalTest.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java (3)
backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetRequest.java (1)
Getter(14-29)backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java (1)
Entity(11-59)backend/src/main/java/org/sejongisc/backend/betting/entity/Stock.java (1)
Entity(11-33)
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (1)
backend/src/main/java/org/sejongisc/backend/point/service/PointHistoryService.java (1)
Service(33-104)
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (1)
backend/src/main/java/org/sejongisc/backend/common/auth/springsecurity/CustomUserDetailsService.java (1)
RequiredArgsConstructor(14-30)
⏰ 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: Backend Gate
🔇 Additional comments (4)
backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java (1)
11-16: 정렬 기준 확인 요청: settleAt NULL 처리PostgreSQL에서 DESC 정렬 시 NULL이 먼저 옵니다. 미정산 라운드를 상단에 노출하려는 의도인지 확인 부탁드립니다.
backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java (1)
56-61: 베팅 도메인 에러 코드 추가 LGTM상태코드/메시지 모두 적절합니다. 서비스 계층 매핑만 일관되게 정리하면 됩니다.
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (2)
51-76: 엔티티 직접 반환에 따른 Lazy 로딩 예외 방지
- BettingController에서 List 엔티티를 그대로 반환할 경우
- Open-Entity-Manager-In-View가 비활성화된 환경에서 LazyInitializationException 발생 가능
- UserBetDto 등 DTO로 변환해 반환하도록 방어 코드를 추가하세요
- application.properties(yml)에서 spring.jpa.open-in-view 설정 여부도 확인 바랍니다
61-68: SecurityFilterChain에서 .anyRequest().authenticated()가 모든 엔드포인트를 보호하므로/user-bets경로는 이미 인증이 필요합니다. 추가 어노테이션 불필요합니다.
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/point/service/PointHistoryService.java
Show resolved
Hide resolved
discipline24
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다~
There was a problem hiding this 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)
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (1)
69-75: 엔드포인트 구현이 올바르며, 향후 페이지네이션 고려 권장인증된 사용자의 베팅 이력을 조회하는 엔드포인트가 올바르게 구현되었습니다. 인증 주체를 통해 사용자별로 데이터가 적절히 격리되어 있습니다.
다만, 사용자의 베팅 이력이 많아질 경우 전체 목록을 한 번에 반환하면 성능 문제가 발생할 수 있습니다. 서비스 레이어의 TODO 주석에서 이미 언급된 것처럼, 향후 페이지네이션 및 필터링 기능을 추가하는 것을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java(5 hunks)
🔇 Additional comments (5)
backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java (1)
13-22: LGTM! 이전 리뷰 피드백 반영 완료DB 레벨에서 (round_id, user_id) 유니크 제약과 인덱스를 추가하여 동시 요청에서 발생할 수 있는 중복 베팅 레이스 컨디션을 효과적으로 방지했습니다. 제약 조건과 인덱스명도 명확하고, 컬럼명도 정확합니다.
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (4)
58-61: LGTM! 베팅 이력 조회 메서드 구현 완료사용자별 베팅 이력을 정산일 기준 내림차순으로 조회하는 메서드가 올바르게 구현되었습니다. TODO 주석에서 향후 필터링 및 검색 기능 추가를 이미 인지하고 있으므로, 현재 구현은 적절합니다.
97-98: LGTM! 베팅 시간 경계 로직 개선lockAt을 배타적 경계로 처리하여 [openAt, lockAt) 구간에서만 베팅을 허용하도록 수정되었습니다. 이는 이전 리뷰 피드백을 반영한 것으로, lockAt 시각에는 베팅이 불가능하도록 하는 자연스러운 정책입니다.
124-129: LGTM! DB 제약 위반 시 중복 베팅 예외 처리 추가
DataIntegrityViolationException을BET_DUPLICATE로 매핑하여 동시 요청에서 발생할 수 있는 레이스 컨디션을 방어합니다. 이는 이전 리뷰에서 요청된 사항을 정확히 구현한 것입니다.라인 91의 애플리케이션 레벨 체크와 DB 레벨 유니크 제약이 함께 작동하여 다층 방어를 제공합니다.
139-139: LGTM! 베팅 취소 시간 검증 로직 일관성 유지lockAt 시각을 경계로 베팅 취소를 차단하는 로직이 postUserBet의 시간 검증 로직과 일관되게 적용되었습니다. lockAt 시각 이후에는 베팅 취소가 불가능하도록 적절히 구현되어 있습니다.
Summary by CodeRabbit
릴리즈 노트
새로운 기능
버그 수정