From 40ea7680c5d7b70a5800adc696bd27c671a1d944 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 1 Jul 2026 12:36:02 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(can):=20=EC=BA=94=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=A1=B0=ED=9A=8C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/can/CanService.kt | 2 ++ .../kr/co/vividnext/sodalive/can/CanServiceTest.kt | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt index 39680d37..b24964b9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt @@ -6,10 +6,12 @@ import kr.co.vividnext.sodalive.common.CountryContext import kr.co.vividnext.sodalive.member.Member import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional import java.time.ZoneId import java.time.format.DateTimeFormatter @Service +@Transactional(readOnly = true) class CanService( private val repository: CanRepository, private val countryContext: CountryContext diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/can/CanServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/can/CanServiceTest.kt index cbfcef2c..53f2b4a9 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/can/CanServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/can/CanServiceTest.kt @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.springframework.data.domain.PageRequest +import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime class CanServiceTest { @@ -134,6 +135,14 @@ class CanServiceTest { assertEquals("캐릭터 톡 이용권 구매", result[5].title) } + @Test + @DisplayName("캔 서비스 조회 로직은 read-only 트랜잭션 경계에서 실행된다") + fun `should run can service queries in read only transaction`() { + val transactional = CanService::class.java.getAnnotation(Transactional::class.java) + + assertEquals(true, transactional.readOnly) + } + private fun createUseCanDto( usage: CanUsage, can: Int, -- 2.49.1 From 5adbae3213398776f9f9ab9702a7886ae4d68683 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 1 Jul 2026 14:00:09 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(channel-donation):=20=ED=9B=84=EC=9B=90?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=9A=8C=EC=9B=90=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=EC=9D=84=20=EB=B3=B4=EC=9E=A5=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChannelDonationMessageRepository.kt | 1 + .../channelDonation/ChannelDonationService.kt | 1 + .../ChannelDonationMessageRepositoryTest.kt | 31 +++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepository.kt index 6938c582..b5f18b33 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepository.kt @@ -52,6 +52,7 @@ class ChannelDonationMessageQueryRepositoryImpl( return queryFactory .selectFrom(channelDonationMessage) + .leftJoin(channelDonationMessage.member).fetchJoin() .where(where) .offset(offset) .limit(limit) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationService.kt index b0ccc1f6..9cb46f3a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationService.kt @@ -18,6 +18,7 @@ import java.time.format.DateTimeFormatter import java.time.temporal.TemporalAdjusters @Service +@Transactional(readOnly = true) class ChannelDonationService( private val canPaymentService: CanPaymentService, private val memberRepository: MemberRepository, diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepositoryTest.kt index 42502116..3b8f9148 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/explorer/profile/channelDonation/ChannelDonationMessageRepositoryTest.kt @@ -133,6 +133,37 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor( assertEquals(3, totalCount) } + @Test + @DisplayName("목록 조회 시 후원자 회원 정보를 함께 로딩한다") + fun shouldFetchMemberWithDonationMessages() { + // given: 크리에이터/후원자와 조회 대상 메시지를 준비한다. + val creator = saveMember(nickname = "creator3", role = MemberRole.CREATOR) + val viewer = saveMember(nickname = "viewer3", role = MemberRole.USER) + val message = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false) + val monthStart = LocalDateTime.now().withDayOfMonth(1).toLocalDate().atStartOfDay() + val nextMonthStart = monthStart.plusMonths(1) + updateCreatedAt(message.id!!, monthStart.plusDays(1)) + entityManager.flush() + entityManager.clear() + + // when: 목록을 조회한 뒤 영속성 컨텍스트를 비운다. + val list = channelDonationMessageRepository.getChannelDonationMessageList( + creatorId = creator.id!!, + memberId = viewer.id!!, + isCreator = false, + offset = 0, + limit = 10, + startDateTime = monthStart, + endDateTime = nextMonthStart + ) + entityManager.clear() + + // then: 조회 결과의 후원자 정보는 세션 없이도 접근 가능해야 한다. + assertEquals(1, list.size) + assertEquals(viewer.id, list[0].member!!.id) + assertEquals("viewer3", list[0].member!!.nickname) + } + private fun saveMember(nickname: String, role: MemberRole): Member { return memberRepository.saveAndFlush( Member( -- 2.49.1