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/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/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, 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(