Conversation
개요새로운 매치 취소 기능을 관리자 계층과 매치 도메인에 추가합니다. 관리자용 엔드포인트, 서비스 메서드, 도메인 로직, 및 상태 검증을 도입하여 매치 생명주기 관리를 확장합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Admin as Admin User
participant Controller as AdminController
participant AdminSvc as AdminService
participant MatchSvc as MatchService
participant MatchRepo as MatchRepository
participant MatchRoom as MatchRoom Entity
Admin->>Controller: PATCH /matches/{matchId}/cancel
Controller->>AdminSvc: cancelMatch(matchId)
AdminSvc->>MatchSvc: cancelMatch(matchId)
MatchSvc->>MatchRepo: findById(matchId)
MatchRepo-->>MatchSvc: MatchRoom
alt MatchRoom not found
MatchSvc-->>MatchSvc: throw MATCH_NOT_FOUND
else Status is terminal
MatchSvc->>MatchSvc: check if CANCELLED/CLOSED/COMPLETED
MatchSvc-->>MatchSvc: throw MATCH_CANNOT_CANCEL
else Status is valid
MatchSvc->>MatchRoom: cancel()
MatchRoom->>MatchRoom: setStatus(CANCELLED)
MatchRoom-->>MatchSvc: success
end
MatchSvc-->>AdminSvc: void
AdminSvc-->>Controller: void
Controller-->>Admin: 200 OK {success message}
예상 코드 리뷰 소요 시간🎯 2 (Simple) | ⏱️ ~12분 관련된 가능성 있는 PR
시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java`:
- Line 7: The javadoc/comment for the MatchStatus enum value COMPLETED contains
a broken/garbled fragment ("...필요 시COM"); open the MatchStatus enum and fix the
comment for COMPLETED to a clear, complete sentence (e.g., "매칭 종료 (scheduledAt +
durationMinutes 이후) — 매칭 완료 후 리뷰/평점 처리 및 포인트 지급 등의 사이드 이펙트 처리"), removing the
stray characters and ensuring the meaning is unambiguous.
In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`:
- Around line 130-142: The cancelMatch method can suffer TOCTOU races because it
reads with matchRoomRepository.findById then checks status before cancelling;
add a pessimistic write lock to serialize concurrent cancels by adding a
repository method (e.g. findByIdForUpdate) annotated with
`@Lock`(LockModeType.PESSIMISTIC_WRITE) and use that method inside cancelMatch to
load the MatchRoom (instead of findById), then perform the same status checks
and call matchRoom.cancel(); keep the same exception flows (CustomException with
MatchErrorCode) so behavior is unchanged but the read-modify is now atomic.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/main/java/com/be/sportizebe/domain/admin/controller/AdminController.javasrc/main/java/com/be/sportizebe/domain/admin/service/AdminService.javasrc/main/java/com/be/sportizebe/domain/admin/service/AdminServiceImpl.javasrc/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.javasrc/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.javasrc/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.javasrc/main/java/com/be/sportizebe/domain/match/service/MatchService.javasrc/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java
| OPEN, // 참여 가능 | ||
| FULL, // 정원 마감 | ||
| CLOSED, // 매칭 시작됨 (scheduledAt 이후, 참여 불가) -> "지금 진행중인 매칭" 탭 보여줄 때 | ||
| COMPLETED, // 매칭 종료 (scheduledAt + durationMinutes 이후) -> "매칭 완료 후 리뷰/평점. 포인트 지금 등 사이드 이펙트 필요 시COM |
There was a problem hiding this comment.
주석 문구가 중간에서 깨져 의미 전달이 어렵습니다.
Line 7의 ...필요 시COM은 오탈자/잔여 문자열로 보이며, 상태 정의 주석은 배포 전 정리하는 것이 좋겠습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchStatus.java` at line
7, The javadoc/comment for the MatchStatus enum value COMPLETED contains a
broken/garbled fragment ("...필요 시COM"); open the MatchStatus enum and fix the
comment for COMPLETED to a clear, complete sentence (e.g., "매칭 종료 (scheduledAt +
durationMinutes 이후) — 매칭 완료 후 리뷰/평점 처리 및 포인트 지급 등의 사이드 이펙트 처리"), removing the
stray characters and ensuring the meaning is unambiguous.
| @Override | ||
| public void cancelMatch(Long matchId) { | ||
| MatchRoom matchRoom = matchRoomRepository.findById(matchId) | ||
| .orElseThrow(() -> new CustomException(MatchErrorCode.MATCH_NOT_FOUND)); | ||
|
|
||
| if (matchRoom.getStatus() == MatchStatus.CANCELLED | ||
| || matchRoom.getStatus() == MatchStatus.CLOSED | ||
| || matchRoom.getStatus() == MatchStatus.COMPLETED) { | ||
| throw new CustomException(MatchErrorCode.MATCH_CANNOT_CANCEL); | ||
| } | ||
|
|
||
| matchRoom.cancel(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the MatchRoom entity and MatchRoomRepository files
find src/main/java/com/be/sportizebe -name "MatchRoom.java" -o -name "MatchRoomRepository.java"Repository: Sportize/be
Length of output: 202
🏁 Script executed:
# Check for `@Version`, `@Lock`, and locking-related annotations in the match domain
rg -n "@Version|@Lock|LockModeType|pessimistic|optimistic" src/main/java/com/be/sportizebe/domain/match --type=javaRepository: Sportize/be
Length of output: 37
🏁 Script executed:
# Find the MatchRoomRepository and examine its current methods
fd "MatchRoomRepository" src/main/java --type=javaRepository: Sportize/be
Length of output: 226
🏁 Script executed:
# Check the MatchRoom entity for version control
fd "MatchRoom\.java" src/main/java --type=java | head -5Repository: Sportize/be
Length of output: 226
🏁 Script executed:
# Look for existing usage of locking patterns in similar cancel/update operations
rg -n "findByIdForUpdate|PESSIMISTIC_WRITE" src/main/java/com/be/sportizebe --type=javaRepository: Sportize/be
Length of output: 37
🏁 Script executed:
# Read the MatchRoom entity to check for `@Version` and other annotations
cat -n src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java | head -100Repository: Sportize/be
Length of output: 2955
🏁 Script executed:
# Read the MatchRoomRepository to check current methods
cat -n src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.javaRepository: Sportize/be
Length of output: 2113
🏁 Script executed:
# Search for locking annotations/imports across match domain
rg -n "@Version|@Lock|LockModeType" src/main/java/com/be/sportizebe/domain/matchRepository: Sportize/be
Length of output: 37
🏁 Script executed:
# Check for pessimistic/optimistic locking patterns
rg -n "findByIdForUpdate|PESSIMISTIC_WRITE|OPTIMISTIC" src/main/java/com/be/sportizebe/domainRepository: Sportize/be
Length of output: 37
🏁 Script executed:
# Read the full MatchServiceImpl to understand the context better
cat -n src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java | sed -n '120,150p'Repository: Sportize/be
Length of output: 1559
🏁 Script executed:
# Check if cancelMatch method has `@Transactional` or other synchronization annotations
cat -n src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java | head -50Repository: Sportize/be
Length of output: 2596
🏁 Script executed:
# Check class-level annotations on MatchServiceImpl
rg -A 10 "class MatchServiceImpl" src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.javaRepository: Sportize/be
Length of output: 445
🏁 Script executed:
# Check if there are any other concurrent cancel/update patterns in the codebase that use locking
rg -B 2 "cancel\(\)" src/main/java/com/be/sportizebe/domain --context=3Repository: Sportize/be
Length of output: 1349
동시 취소 요청 시 상태 체크와 변경이 원자적이지 않아 중복 성공이 발생할 수 있습니다.
@Transactional만으로는 애플리케이션 계층의 TOCTOU 경합 조건을 방지하지 못합니다. 현재 cancelMatch() 메서드는:
- findById()로 조회
- 상태 조건 검사 (라인 135-138)
- cancel() 호출로 변경
이 경우 동시 요청이 모두 상태 검사를 통과한 후 실행될 수 있으므로, 두 번째 요청도 성공하게 됩니다. MatchRoom 엔티티에 @Version이 없고 MatchRoomRepository에 락 메서드가 없는 상태이므로, 비관적 락(PESSIMISTIC_WRITE)을 추가하여 원자성을 보장해야 합니다.
🔧 제안 수정안
- MatchRoom matchRoom = matchRoomRepository.findById(matchId)
+ MatchRoom matchRoom = matchRoomRepository.findByIdForUpdate(matchId)
.orElseThrow(() -> new CustomException(MatchErrorCode.MATCH_NOT_FOUND));// MatchRoomRepository.java (추가)
`@Lock`(LockModeType.PESSIMISTIC_WRITE)
`@Query`("select m from MatchRoom m where m.id = :matchId")
Optional<MatchRoom> findByIdForUpdate(`@Param`("matchId") Long matchId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`
around lines 130 - 142, The cancelMatch method can suffer TOCTOU races because
it reads with matchRoomRepository.findById then checks status before cancelling;
add a pessimistic write lock to serialize concurrent cancels by adding a
repository method (e.g. findByIdForUpdate) annotated with
`@Lock`(LockModeType.PESSIMISTIC_WRITE) and use that method inside cancelMatch to
load the MatchRoom (instead of findById), then perform the same status checks
and call matchRoom.cancel(); keep the same exception flows (CustomException with
MatchErrorCode) so behavior is unchanged but the read-modify is now atomic.
#️⃣ Issue Number
📝 요약(Summary)
MatchStatusenum에CLOSED,COMPLETED,CANCELLED추가MatchRoom에cancel()도메인 메서드 추가PATCH /api/admin/matches/{matchId}/cancel)CANCELLED/CLOSED/COMPLETED상태인 매칭은 취소 불가 (MATCH_CANNOT_CANCEL)🛠️ PR 유형
어떤 변경 사항이 있나요?
📸스크린샷 (선택)
💬 공유사항 to 리뷰어
✅ PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.
Summary by CodeRabbit
새로운 기능