READY 상태의 정산 건을 중복 조회하여 정산금이 두 번 입금되는 치명적 오류 발생 가능성.SELECT) 시점과 상태 변경(UPDATE) 시점 사이의 간극으로 인한 경쟁 상태(Race Condition).PESSIMISTIC_WRITE 락을 걸어 타 트랜잭션의 접근을 원천 차단.일반 조회 쿼리 사용. 동시 접근 시 A 트랜잭션이 커밋하기 전 B 트랜잭션도 데이터를 읽어갈 수 있음.
public interface SettlementRepository extends JpaRepository<Settlement, Long> {
// 락 없음: 동시성 이슈 발생 위험
@Query("SELECT s FROM Settlement s JOIN FETCH s.seller WHERE s.status = :status")
List<Settlement> findAllByStatus(SettlementStatus status, Pageable pageable);
}
비관적 락 적용
public interface SettlementRepository extends JpaRepository<Settlement, Long> {
// 비관적 락 적용
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT s FROM Settlement s JOIN FETCH s.seller WHERE s.status = :status")
List<Settlement> findAllByStatus(SettlementStatus status, Pageable pageable);
}
Stream.sorted()를 통해 오름차순으로 정렬하여 락 획득 순서를 강제함.ID 추출 시 정렬 과정 없음. 입력 데이터 순서에 따라 락 순서가 뒤죽박죽되어 교착 상태
// 정렬되지 않은 ID 리스트
List<Long> memberIds = settlements.stream()
.map(s -> s.getSeller().getId())
.collect(Collectors.toList()); // [50, 10, 30] 순서일 수 있음
// 결과: 50 -> 10 -> 30 순서로 락 시도 (다른 트랜잭션과 충돌 가능)
paymentSupport.findWalletsByMemberIdsForUpdate(memberIds);