From fefb5c24eb8a4eea0f4c79307e7f1388b1d2395e Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 18 May 2026 14:52:08 +0900 Subject: [PATCH] =?UTF-8?q?fix(charge):=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A6=89=EC=8B=9C=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=EC=9D=84=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../can/charge/event/ChargeEventJobService.kt | 7 ++++- .../charge/event/ChargeEventJobServiceTest.kt | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobService.kt index 8f0b3b9e..60da8940 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobService.kt @@ -18,6 +18,7 @@ import org.springframework.context.ApplicationEventPublisher import org.springframework.dao.DataIntegrityViolationException import org.springframework.stereotype.Service import org.springframework.transaction.PlatformTransactionManager +import org.springframework.transaction.TransactionDefinition import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.support.TransactionSynchronization import org.springframework.transaction.support.TransactionSynchronizationManager @@ -37,7 +38,11 @@ class ChargeEventJobService( private val applicationEventPublisher: ApplicationEventPublisher? = null, transactionManager: PlatformTransactionManager? = null ) { - private val transactionTemplate = transactionManager?.let { TransactionTemplate(it) } + private val transactionTemplate = transactionManager?.let { + TransactionTemplate(it).also { template -> + template.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW + } + } @Transactional fun createAndProcessImmediate(sourceChargeId: Long, memberId: Long): ChargeEventJob? { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobServiceTest.kt index fa954182..8f581f6a 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/can/charge/event/ChargeEventJobServiceTest.kt @@ -10,6 +10,10 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.springframework.transaction.TransactionDefinition +import org.springframework.transaction.support.AbstractPlatformTransactionManager +import org.springframework.transaction.support.DefaultTransactionStatus +import org.springframework.transaction.support.TransactionTemplate import java.time.LocalDateTime class ChargeEventJobServiceTest { @@ -61,6 +65,19 @@ class ChargeEventJobServiceTest { Mockito.verify(memberRepository, Mockito.never()).findByIdForUpdate(Mockito.anyLong()) } + @Test + fun shouldUseRequiresNewTransactionForAfterCommitImmediateProcessing() { + val service = ChargeEventJobService( + jobRepository = Mockito.mock(ChargeEventJobRepository::class.java), + chargeRepository = Mockito.mock(ChargeRepository::class.java), + memberRepository = Mockito.mock(MemberRepository::class.java), + transactionManager = RecordingTransactionManager() + ) + val transactionTemplate = service.transactionTemplateForTest() + + assertEquals(TransactionDefinition.PROPAGATION_REQUIRES_NEW, transactionTemplate.propagationBehavior) + } + private fun job( id: Long, memberId: Long = 10L, @@ -87,3 +104,16 @@ class ChargeEventJobServiceTest { return Member(password = "pw", nickname = "tester").also { it.id = id } } } + +private fun ChargeEventJobService.transactionTemplateForTest(): TransactionTemplate { + val field = ChargeEventJobService::class.java.getDeclaredField("transactionTemplate") + field.isAccessible = true + return field.get(this) as TransactionTemplate +} + +private class RecordingTransactionManager : AbstractPlatformTransactionManager() { + override fun doGetTransaction(): Any = Any() + override fun doBegin(transaction: Any, definition: TransactionDefinition) {} + override fun doCommit(status: DefaultTransactionStatus) {} + override fun doRollback(status: DefaultTransactionStatus) {} +}