diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftRepository.kt index 376fafde..8048ff04 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftRepository.kt @@ -4,13 +4,19 @@ import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Lock import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param import java.time.LocalDateTime +import javax.persistence.LockModeType interface GiftRepository : JpaRepository { fun findByApplicationNo(applicationNo: String): Gift? + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select g from Gift g where g.applicationNo = :applicationNo") + fun findByApplicationNoForUpdate(@Param("applicationNo") applicationNo: String): Gift? + fun findBySenderMemberId(senderMemberId: Long, pageable: Pageable): Page fun findByRecipientMemberIdAndStatusIn( @@ -74,6 +80,7 @@ interface GiftRepository : JpaRepository { ) """ ) + @Lock(LockModeType.PESSIMISTIC_WRITE) fun findReceivedGiftsWithTrackingDeadlineBefore(@Param("now") now: LocalDateTime): List @Query( @@ -113,6 +120,7 @@ interface GiftRepository : JpaRepository { ) """ ) + @Lock(LockModeType.PESSIMISTIC_WRITE) fun findReceivedGiftsWithTrackingDeadlineReminderDue( @Param("now") now: LocalDateTime, @Param("reminderUntil") reminderUntil: LocalDateTime diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt index a22cd12c..86f1c82e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt @@ -207,7 +207,7 @@ class GiftCommandService( @Transactional fun cancelGift(member: Member, applicationNo: String): GiftCancellationResult { val senderMemberId = member.id ?: throw invalidRequest() - val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest() + val gift = giftRepository.findByApplicationNoForUpdate(applicationNo) ?: throw invalidRequest() if (gift.senderMemberId != senderMemberId || gift.status != GiftStatus.RECEIVED) throw invalidRequest() val canceledAt = now() @@ -231,7 +231,7 @@ class GiftCommandService( command: GiftTrackingRegistrationCommand ): GiftTrackingRegistrationResult { val senderMemberId = member.id ?: throw invalidRequest() - val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest() + val gift = giftRepository.findByApplicationNoForUpdate(applicationNo) ?: throw invalidRequest() if (gift.senderMemberId != senderMemberId || gift.status != GiftStatus.RECEIVED) throw invalidRequest() validate(command) val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest() diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt index 4c9e5e17..a7cf2efb 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt @@ -546,7 +546,7 @@ class GiftCommandServiceTest { senderAddress = "서울시 강남구", trackingDeadlineAt = NOW.plusDays(3) ) - Mockito.`when`(giftRepository.findByApplicationNo(APPLICATION_NO)).thenReturn(cancellationGift) + Mockito.`when`(giftRepository.findByApplicationNoForUpdate(APPLICATION_NO)).thenReturn(cancellationGift) Mockito.`when`(deliveryRepository.findByGiftId(GIFT_ID)).thenReturn(cancellationDelivery) } @@ -579,7 +579,7 @@ class GiftCommandServiceTest { senderAddress = "서울시 강남구", trackingDeadlineAt = NOW.plusDays(3) ) - Mockito.`when`(giftRepository.findByApplicationNo(APPLICATION_NO)).thenReturn(trackingGift) + Mockito.`when`(giftRepository.findByApplicationNoForUpdate(APPLICATION_NO)).thenReturn(trackingGift) Mockito.`when`(deliveryRepository.findByGiftId(GIFT_ID)).thenReturn( trackingDelivery.takeIf { deliveryExists } )