Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import com.process.clash.application.compete.realtime.CompeteRefetchNotifier;
import com.process.clash.application.compete.rival.rival.data.ApplyRivalData;
import com.process.clash.application.compete.rival.rival.exception.exception.conflict.AlreadyAppliedRivalException;
import com.process.clash.application.compete.rival.rival.policy.ApplyRivalPolicy;
import com.process.clash.application.compete.rival.rival.port.in.ApplyRivalUseCase;
import com.process.clash.application.compete.rival.rival.port.out.RivalRepositoryPort;
import com.process.clash.application.user.usernotice.port.out.UserNoticeRepositoryPort;
import com.process.clash.domain.rival.rival.entity.Rival;
import com.process.clash.domain.user.usernotice.entity.UserNotice;
import com.process.clash.domain.user.usernotice.enums.NoticeCategory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -35,7 +37,12 @@ public void execute(ApplyRivalData.Command command) {
.map(opponentId -> Rival.createDefault(command.actor().id(), opponentId.id()))
.toList();

List<Rival> savedRivals = rivalRepositoryPort.saveAll(rivals);
List<Rival> savedRivals;
try {
savedRivals = rivalRepositoryPort.saveAll(rivals);
} catch (DataIntegrityViolationException e) {
throw new AlreadyAppliedRivalException();
}

List<Long> savedOpponentIds = savedRivals.stream()
.map(Rival::secondUserId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
-- 레이스 컨디션으로 생성된 중복 PENDING 레코드 정리 후 unique partial index 추가
--
-- 제거 대상:
-- 1) 같은 쌍 사이에 PENDING-PENDING 레코드가 2개인 경우 → ID가 큰 것(나중에 생긴 것) 제거
-- 2) 같은 쌍 사이에 이미 ACCEPTED가 존재하는데 PENDING도 남아있는 경우 → PENDING 제거
--
-- user_notices.rival_id 는 ON DELETE SET NULL 이므로 rivals 삭제 전에 먼저 직접 삭제해야
-- orphan notice가 남지 않음

-- Step 1: 제거할 라이벌과 연결된 알림 먼저 삭제
DELETE FROM user_notices
WHERE rival_id IN (
SELECT r2.id
FROM rivals r1
JOIN rivals r2
ON LEAST(r1.fk_first_user_id, r1.fk_second_user_id) = LEAST(r2.fk_first_user_id, r2.fk_second_user_id)
AND GREATEST(r1.fk_first_user_id, r1.fk_second_user_id) = GREATEST(r2.fk_first_user_id, r2.fk_second_user_id)
AND r1.id < r2.id
AND r1.rival_linking_status = 'PENDING'
AND r2.rival_linking_status = 'PENDING'

UNION

SELECT pending.id
FROM rivals pending
JOIN rivals accepted
ON LEAST(pending.fk_first_user_id, pending.fk_second_user_id) = LEAST(accepted.fk_first_user_id, accepted.fk_second_user_id)
AND GREATEST(pending.fk_first_user_id, pending.fk_second_user_id) = GREATEST(accepted.fk_first_user_id, accepted.fk_second_user_id)
AND pending.rival_linking_status = 'PENDING'
AND accepted.rival_linking_status = 'ACCEPTED'
);

-- Step 2: 중복/고아 라이벌 레코드 삭제
DELETE FROM rivals
WHERE id IN (
SELECT r2.id
FROM rivals r1
JOIN rivals r2
ON LEAST(r1.fk_first_user_id, r1.fk_second_user_id) = LEAST(r2.fk_first_user_id, r2.fk_second_user_id)
AND GREATEST(r1.fk_first_user_id, r1.fk_second_user_id) = GREATEST(r2.fk_first_user_id, r2.fk_second_user_id)
AND r1.id < r2.id
AND r1.rival_linking_status = 'PENDING'
AND r2.rival_linking_status = 'PENDING'

UNION

SELECT pending.id
FROM rivals pending
JOIN rivals accepted
ON LEAST(pending.fk_first_user_id, pending.fk_second_user_id) = LEAST(accepted.fk_first_user_id, accepted.fk_second_user_id)
AND GREATEST(pending.fk_first_user_id, pending.fk_second_user_id) = GREATEST(accepted.fk_first_user_id, accepted.fk_second_user_id)
AND pending.rival_linking_status = 'PENDING'
AND accepted.rival_linking_status = 'ACCEPTED'
);

-- Step 3: 동일 유저 쌍에 대해 PENDING/ACCEPTED 상태의 레코드가 중복 생성되는 것을 방지
-- LEAST/GREATEST로 방향(firstUser/secondUser)과 무관하게 정규화
CREATE UNIQUE INDEX uq_rivals_active_pair
ON rivals (
LEAST(fk_first_user_id, fk_second_user_id),
GREATEST(fk_first_user_id, fk_second_user_id)
)
WHERE rival_linking_status IN ('PENDING', 'ACCEPTED');
Loading
Loading