Skip to content

[BE] [FEAT] 베팅 정산 시스템 고도화 및 낙관적 락 재시도 어노테이션 도입#190

Merged
Yooonjeong merged 24 commits intomainfrom
20260118_#188_배당률_기반_보상_정산_로직_구현
Jan 22, 2026

Hidden character warning

The head ref may contain hidden characters: "20260118_#188_\ubc30\ub2f9\ub960_\uae30\ubc18_\ubcf4\uc0c1_\uc815\uc0b0_\ub85c\uc9c1_\uad6c\ud604"
Merged

[BE] [FEAT] 베팅 정산 시스템 고도화 및 낙관적 락 재시도 어노테이션 도입#190
Yooonjeong merged 24 commits intomainfrom
20260118_#188_배당률_기반_보상_정산_로직_구현

Conversation

@Yooonjeong
Copy link
Contributor

@Yooonjeong Yooonjeong commented Jan 19, 2026

Summary by CodeRabbit

  • New Features

    • 무승부 처리 추가: 최종 가격이 이전 종가와 같을 때 베팅을 무승부로 처리해 원금 환급.
    • 활성 라운드 응답 개선: 클라이언트용 현재 라운드 정보가 DTO 형태로 제공.
  • Bug Fixes

    • 결과 판정 수정: 동률을 잘못 상승으로 판정하던 문제 해결.
    • 배당 계산 개선: 무료 베팅 고정 보상 유지, 유료 배당 산정 보강.
  • Refactor

    • 스케줄러 통합 및 정산 흐름 개선: 일간/주간 흐름 단순화 및 배치 정산 효율화.

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

@Yooonjeong Yooonjeong linked an issue Jan 19, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

Walkthrough

BetRound과 UserBet에 무승부(draw) 처리 로직을 추가하고, 낙관적 재시도 어노테이션(@OptimisticRetry)과 AOP(OptimisticRetryAspect)를 도입했습니다. BettingService의 베팅/취소/정산 흐름과 스케줄러가 재구성되었고, PointHistoryService의 내부 재시도 로직이 제거되었으며, UserBetRepository 쿼리가 확장되었습니다.

Changes

Cohort / File(s) 변경 요약
무승부 처리
backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java, backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java
BetRound.isDraw() 추가; determineResult()가 이전종가와 동일하면 null 반환(무승부). UserBet.draw() 추가 — 원금 환급(payoutPoints = stakePoints), isCorrect=false, betStatus=CLOSED.
낙관적 재시도(어노테이션·Aspect)
backend/src/main/java/org/sejongisc/backend/common/annotation/OptimisticRetry.java, backend/src/main/java/org/sejongisc/backend/common/aspect/OptimisticRetryAspect.java
@OptimisticRetry(maxAttempts, backoff) 추가. Aspect로 어노테이션이 붙은 메서드를 가로채 OptimisticLockingFailureException에 대해 재시도 템플릿으로 처리(재시도 정책, backoff, 실패 시 CONCURRENT_UPDATE 변환 등).
BettingService 재구성
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java
getActiveRound(Scope) 제거 및 getActiveRoundResponse(Scope) 추가. postUserBet, cancelUserBet, settleUserBets 등에 @OptimisticRetry 적용, 트랜잭션 어노테이션 조정, 무승부 분기·배치형 정산 도입, 보상 계산 로직(calculateReward) 수정.
스케줄러 통합
backend/src/main/java/org/sejongisc/backend/betting/service/BettingScheduler.java
주별 스케줄러 제거 및 통합: openScheduler(), closeScheduler(), settleScheduler()로 통합하고 내부에서 요일(월요일) 검사로 주간 생성 조건 처리.
PointHistoryService 단순화
backend/src/main/java/org/sejongisc/backend/point/service/PointHistoryService.java
createPointHistory의 Retryable/Recover 기반 재시도 및 OptimisticLockingFailureException 복구 로직 제거; 외부에서 낙관적 재시도 사용 권장으로 Javadoc 갱신.
리포지토리·쿼리 변경
backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java
findByUserBetIdAndUserIdWithRound(UUID, UUID) 커스텀 쿼리 추가(라운드 선행 패치). findAllByRoundIn(List<BetRound>) 추가로 다중 라운드 조회 지원; 기존 단일 라운드 메서드 대체/변경.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OptimisticRetryAspect
    participant BettingService
    participant Database
    participant PointHistoryService

    Client->>BettingService: settleUserBets()
    BettingService->>OptimisticRetryAspect: (proxy) doRetry(...)
    OptimisticRetryAspect->>BettingService: proceed()
    BettingService->>Database: find active BetRounds & price data
    Database-->>BettingService: BetRounds, prices
    BettingService->>BettingService: determineResult() per round
    alt result == null (draw)
        BettingService->>Database: find UserBets by round(s)
        Database-->>BettingService: UserBets
        loop each UserBet
            BettingService->>UserBet: draw()
            BettingService->>PointHistoryService: createPointHistory(refund)
            PointHistoryService-->>Database: persist history
            Database-->>BettingService: saved
        end
    else result != null
        BettingService->>Database: find UserBets by round(s)
        Database-->>BettingService: UserBets
        loop winners/losers
            BettingService->>BettingService: calculateReward()
            BettingService->>PointHistoryService: createPointHistory(payout)
            PointHistoryService-->>Database: persist history
            Database-->>BettingService: saved
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • discipline24
  • cksdid202

Poem

🐰 코드가 폴짝, 무승부면 돌려줘요 —
배당은 공정히, 원금은 빛나고,
재시도는 살짝, 로그는 반짝,
스케줄은 합쳐져 한결 단정해,
토끼가 깡충, 축하의 당근 한 조각.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경사항의 주요 내용을 명확하게 반영합니다. 베팅 정산 시스템 고도화와 낙관적 락 재시도 어노테이션 도입이라는 핵심 변경사항이 제목에 잘 드러나 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

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

Caution

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

⚠️ Outside diff range comments (1)
backend/src/main/java/org/sejongisc/backend/betting/service/BettingScheduler.java (1)

9-27: 월요일 판정 타임존을 스케줄 타임존과 일치시키세요.

@Scheduled는 Asia/Seoul인데 LocalDate.now()는 JVM 기본 타임존을 사용합니다. 서버 타임존이 다르면 월요일 여부가 어긋날 수 있어요. ZoneId.of("Asia/Seoul")로 맞추는 게 안전합니다.

🔧 제안 수정
 import java.time.DayOfWeek;
 import java.time.LocalDate;
+import java.time.ZoneId;
@@
-        if (LocalDate.now().getDayOfWeek() == DayOfWeek.MONDAY) {
+        if (LocalDate.now(ZoneId.of("Asia/Seoul")).getDayOfWeek() == DayOfWeek.MONDAY) {
             bettingService.createBetRound(Scope.WEEKLY);
         }

@Yooonjeong
Copy link
Contributor Author

@Retryable 대신 직접 RetryTemplate을 사용한 Aspect를 정의한 이유 (@OptimisticRetry)

기존 포인트 업데이트 로직은 REQUIRES_NEW 전파 속성을 사용하여 별도 트랜잭션으로 처리되었기 때문에 상위 로직에서 예외가 발생해 롤백될 경우 데이터 정합성 문제 발생 가능

  • 기존에 REQUIRES_NEW를 사용한 이유는 @Retryable 적용을 위해서로 예상됨
  • 정합성 보장을 위해 REQUIRED로 전파 속성 변경 필요

→ 트랜잭션 통합 시 재시도 대상을 createPointHistory가 아닌 최상단 메서드로 설정해야 함

  • 향후 포인트가 활용되는 로직이 늘어날 예정인데 매번 @Retryable@Recover 메서드를 중복해서 정의하는 것은 비효율적

→ 낙관적 락 예외 발생 시 재시도하는 공통 로직을 Aspect로 분리하고 @OptimisticRetry 어노테이션 생성한다면

  • 포인트뿐만 아니라 낙관적 락을 사용하는 다른 기능에서 재활용 가능
  • 동일한 재시도 및 복구 메서드를 반복해서 정의할 필요 x

따라서

  1. 포인트를 업데이트해야 하는 상황(createPointHistory 메서드를 호출하는 경우)에는 이를 호출하는 주체 메서드에 @OptimisticRetry 어노테이션을 붙여야 합니다.
  2. 낙관적 락을 적용할 다른 기능들에서도 메서드에 @OptimisticRetry 어노테이션을 붙이면 재시도 로직 적용 가능합니다!

@cksdid202
Copy link
Contributor

고생하셨습니다 ! 타임존 토끼 리뷰는 반영된 것일까요 ??

discipline24
discipline24 previously approved these changes Jan 21, 2026
@Yooonjeong
Copy link
Contributor Author

@cksdid202
코드로 명시하는 게 안전하겠네요! 반영 해놓겠습니당~

cksdid202
cksdid202 previously approved these changes Jan 21, 2026
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: 1

🤖 Fix all issues with AI agents
In
`@backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java`:
- Around line 299-308: The draw-refund path currently calls
pointHistoryService.createPointHistory(...) unconditionally which throws for
amount == 0 (free bets) and also when calculateReward(...) returns 0; before
calling createPointHistory in the round.isDraw() branch, check that the refund
amount is > 0 (compute refund = bet.isFree ? 0 : calculateReward(...)/or
bet.getStakePoints()) and only call pointHistoryService.createPointHistory(...)
when refund > 0, then call bet.draw(); ensure you reference the bet.isFree field
(not bet.isFree()) and validate calculateReward(...) output to avoid creating a
0-point history.

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: 1

🤖 Fix all issues with AI agents
In
`@backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java`:
- Around line 311-321: The reward can be zero due to defensive calculateReward
behavior, which later causes pointHistoryService.createPointHistory to throw;
update BettingService around the bet handling (the block using calculateReward,
bet.win, and pointHistoryService.createPointHistory) to validate the computed
reward is > 0 before calling createPointHistory (and only call bet.win if reward
> 0), and if reward == 0 either log a warning and skip creating point history or
handle as an application-level error according to existing conventions so you
never pass a zero amount into createPointHistory.
🧹 Nitpick comments (1)
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (1)

330-359: 배당률 계산 로직이 명확합니다.

파리뮤추얼 방식의 배당률 계산이 올바르게 구현되었습니다. floor 연산으로 인한 잔여 포인트 처리는 TODO에 명시된 대로 복식부기 도입 시 보완하면 될 것 같습니다.

잔여 포인트 처리를 위한 시스템 계정 연동 로직이 필요하시면 도움드릴 수 있습니다.

Copy link
Contributor

@discipline24 discipline24 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다~~

@Yooonjeong Yooonjeong requested a review from cksdid202 January 22, 2026 05:59
@Yooonjeong Yooonjeong merged commit 62bb334 into main Jan 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[기능개선][BE] 배당률 기반 보상 정산 로직 구현

3 participants