perf(explorer:creator-profile): 라이브방 목록 N+1 제거 및 예약/결제 여부 일괄 조회
- member 연관 로딩에 fetch join 적용으로 N+1 제거 - reservations 컬렉션 접근 제거 → QLiveReservation 기반 방 ID 일괄 조회로 isReservation 계산 - useCan per-room 조회 제거 → 방 ID 집합 일괄 조회(Set)로 isPaid 계산 - 기존 비즈니스 로직(날짜 포맷, 성인/성별 필터, PRIVATE 플래그 등) 유지
This commit is contained in:
@@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||
@@ -374,7 +375,7 @@ class ExplorerQueryRepository(
|
||||
result.addAll(
|
||||
queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.innerJoin(liveRoom.member, member).fetchJoin()
|
||||
.leftJoin(liveRoom.cancel, liveRoomCancel)
|
||||
.where(where)
|
||||
.orderBy(liveRoom.beginDateTime.asc())
|
||||
@@ -388,13 +389,43 @@ class ExplorerQueryRepository(
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimePattern)
|
||||
.withLocale(langContext.lang.locale)
|
||||
|
||||
return result
|
||||
.map {
|
||||
val reservations = it.reservations
|
||||
.filter { reservation ->
|
||||
reservation.member!!.id!! == userMember.id!! && reservation.isActive
|
||||
// N+1 방지: 한 번에 필요한 정보 일괄 조회
|
||||
val roomIds = result.mapNotNull { it.id }.toSet()
|
||||
if (roomIds.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// 사용자 예약 여부를 방 ID 기준으로 일괄 조회
|
||||
val reservationRoomIdSet: Set<Long> = run {
|
||||
// Q 클래스는 의존 파일들에서 사용되는 패턴을 맞춰 import 없이 정규 참조
|
||||
val resIds = queryFactory
|
||||
.select(QLiveReservation.liveReservation.room.id)
|
||||
.from(QLiveReservation.liveReservation)
|
||||
.where(
|
||||
QLiveReservation.liveReservation.room.id.`in`(roomIds)
|
||||
.and(QLiveReservation.liveReservation.member.id.eq(userMember.id))
|
||||
.and(QLiveReservation.liveReservation.isActive.isTrue)
|
||||
)
|
||||
.fetch()
|
||||
resIds.filterNotNull().toSet()
|
||||
}
|
||||
|
||||
// 결제 여부를 방 ID 기준으로 일괄 조회 (CanUsage.LIVE)
|
||||
val paidRoomIdSet: Set<Long> = run {
|
||||
val ids = queryFactory
|
||||
.select(useCan.room.id)
|
||||
.from(useCan)
|
||||
.where(
|
||||
useCan.room.id.`in`(roomIds)
|
||||
.and(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
)
|
||||
.groupBy(useCan.room.id)
|
||||
.fetch()
|
||||
ids.filterNotNull().toSet()
|
||||
}
|
||||
|
||||
return result
|
||||
.map {
|
||||
val beginDateTime = it.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
@@ -403,22 +434,7 @@ class ExplorerQueryRepository(
|
||||
val beginDateTimeUtc = it.beginDateTime
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
|
||||
val isPaid = if (it.channelName != null) {
|
||||
val useCan = queryFactory
|
||||
.selectFrom(useCan)
|
||||
.innerJoin(useCan.member, member)
|
||||
.where(
|
||||
useCan.member.id.eq(member.id)
|
||||
.and(useCan.room.id.eq(it.id!!))
|
||||
.and(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
)
|
||||
.orderBy(useCan.id.desc())
|
||||
.fetchFirst()
|
||||
|
||||
useCan != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
val isPaid = it.channelName != null && paidRoomIdSet.contains(it.id!!)
|
||||
|
||||
LiveRoomResponse(
|
||||
roomId = it.id!!,
|
||||
@@ -441,7 +457,7 @@ class ExplorerQueryRepository(
|
||||
else -> "$cloudFrontHost/$profileImage"
|
||||
}
|
||||
},
|
||||
isReservation = reservations.isNotEmpty(),
|
||||
isReservation = reservationRoomIdSet.contains(it.id!!),
|
||||
isActive = it.isActive,
|
||||
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user