From 6a9e76c13d80deb836723c9d8cf41f3a904c0a3c Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 22 Oct 2026 18:22:57 +0900 Subject: [PATCH] =?UTF-8?q?fix(gift):=20=EC=A0=91=EC=88=98=20=EC=84=A0?= =?UTF-8?q?=EB=AC=BC=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EC=93=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=9E=A0=EA=B8=88=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v2/gift/adapter/out/persistence/GiftRepository.kt | 8 ++++++++ .../sodalive/v2/gift/application/GiftCommandService.kt | 4 ++-- .../vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) 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 } )