OriginalAudioDrama 리스트 조회 쿼리 최적화
OriginalAudioDrama 리스트 조회 시 엔티티 대신 DTO를 직접 조회하도록 개선 콘텐츠 개수, 신규 콘텐츠 여부, 번역 제목을 서브쿼리와 조인을 통해 한 번에 가져오도록 하여 기존의 N+1 문제와 다수의 추가 쿼리 발생을 해결
This commit is contained in:
@@ -131,15 +131,11 @@ class HomeService(
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
// 오직 보이스온에서만
|
||||
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
orderByRandom = true
|
||||
contentType = contentType
|
||||
)
|
||||
|
||||
val translatedOriginalAudioDramaList = getTranslatedSeriesList(seriesList = originalAudioDramaList)
|
||||
|
||||
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
||||
|
||||
// 요일별 시리즈
|
||||
@@ -214,7 +210,7 @@ class HomeService(
|
||||
latestContentList = latestContentList,
|
||||
bannerList = bannerList,
|
||||
eventBannerList = eventBannerList,
|
||||
originalAudioDramaList = translatedOriginalAudioDramaList,
|
||||
originalAudioDramaList = originalAudioDramaList,
|
||||
auditionList = auditionList,
|
||||
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
||||
popularCharacters = translatedPopularCharacters,
|
||||
|
||||
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audi
|
||||
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCurationItem.audioContentCurationItem
|
||||
import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse
|
||||
import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse
|
||||
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation.seriesTranslation
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||
@@ -20,6 +21,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
|
||||
|
||||
@@ -64,12 +66,13 @@ interface ContentSeriesQueryRepository {
|
||||
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
|
||||
fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series>
|
||||
fun getOriginalAudioDramaList(
|
||||
imageHost: String,
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
orderByRandom: Boolean = false,
|
||||
locale: String,
|
||||
offset: Long = 0,
|
||||
limit: Long = 20
|
||||
): List<Series>
|
||||
): List<GetSeriesListResponse.SeriesListItem>
|
||||
|
||||
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
||||
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
||||
@@ -365,12 +368,13 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
override fun getOriginalAudioDramaList(
|
||||
imageHost: String,
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
orderByRandom: Boolean,
|
||||
locale: String,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series> {
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var where = series.isOriginal.isTrue
|
||||
.and(series.isActive.isTrue)
|
||||
|
||||
@@ -392,20 +396,85 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.selectFrom(series)
|
||||
.innerJoin(series.member, member)
|
||||
.where(where)
|
||||
.orderBy(
|
||||
if (orderByRandom) {
|
||||
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
|
||||
} else {
|
||||
series.id.desc()
|
||||
}
|
||||
val now = LocalDateTime.now()
|
||||
val sevenDaysAgo = now.minusDays(7)
|
||||
|
||||
val contentCountSubquery = queryFactory
|
||||
.select(seriesContent.id.count())
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.where(
|
||||
seriesContent.series.id.eq(series.id),
|
||||
audioContent.isActive.isTrue,
|
||||
if (!isAdult) audioContent.isAdult.isFalse else null
|
||||
)
|
||||
|
||||
val isNewSubquery = queryFactory
|
||||
.select(seriesContent.id)
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.where(
|
||||
seriesContent.series.id.eq(series.id),
|
||||
audioContent.isActive.isTrue,
|
||||
if (!isAdult) audioContent.isAdult.isFalse else null,
|
||||
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
val results = queryFactory
|
||||
.select(
|
||||
series.id,
|
||||
series.title,
|
||||
seriesTranslation.renderedPayload,
|
||||
series.coverImage,
|
||||
series.state,
|
||||
member.id,
|
||||
member.nickname,
|
||||
member.profileImage,
|
||||
contentCountSubquery,
|
||||
isNewSubquery.exists(),
|
||||
series
|
||||
)
|
||||
.from(series)
|
||||
.innerJoin(series.member, member)
|
||||
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||
.where(where)
|
||||
.having(contentCountSubquery.gt(0))
|
||||
.orderBy(series.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
|
||||
return results.map { row ->
|
||||
val seriesId = row.get(series.id)!!
|
||||
val originTitle = row.get(series.title)!!
|
||||
val payload = row.get(seriesTranslation.renderedPayload)
|
||||
val translatedTitle = payload?.title
|
||||
val coverImage = row.get(series.coverImage)
|
||||
val state = row.get(series.state)
|
||||
val creatorId = row.get(member.id)!!
|
||||
val nickname = row.get(member.nickname)!!
|
||||
val profileImage = row.get(member.profileImage)
|
||||
val numberOfContent = row.get(8, Long::class.java) ?: 0L
|
||||
val isNew = row.get(9, Boolean::class.java) ?: false
|
||||
val seriesEntity = row.get(series)!!
|
||||
|
||||
GetSeriesListResponse.SeriesListItem(
|
||||
seriesId = seriesId,
|
||||
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||
coverImage = "$imageHost/$coverImage",
|
||||
publishedDaysOfWeek = "", // Service layer will fill this
|
||||
isComplete = state == SeriesState.COMPLETE,
|
||||
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||
creatorId = creatorId,
|
||||
nickname = nickname,
|
||||
profileImage = "$imageHost/$profileImage"
|
||||
),
|
||||
numberOfContent = numberOfContent.toInt(),
|
||||
isNew = isNew,
|
||||
rawPublishedDaysOfWeek = seriesEntity.publishedDaysOfWeek
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int {
|
||||
|
||||
@@ -55,12 +55,21 @@ class ContentSeriesService(
|
||||
fun getOriginalAudioDramaList(
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
orderByRandom: Boolean = false,
|
||||
offset: Long = 0,
|
||||
limit: Long = 20
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, contentType, orderByRandom, offset, limit)
|
||||
return getTranslatedSeriesList(seriesToSeriesListItem(originalAudioDramaList, isAdult, contentType))
|
||||
val originalAudioDramaList = repository.getOriginalAudioDramaList(
|
||||
imageHost = coverImageHost,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
locale = langContext.lang.code,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
return originalAudioDramaList.map { item ->
|
||||
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||
}
|
||||
}
|
||||
|
||||
fun getGenreList(memberId: Long, isAdult: Boolean, contentType: ContentType): List<GetSeriesGenreListResponse> {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package kr.co.vividnext.sodalive.content.series
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
|
||||
data class GetSeriesListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<SeriesListItem>
|
||||
) {
|
||||
data class SeriesListItem(
|
||||
data class SeriesListItem @QueryProjection constructor(
|
||||
val seriesId: Long,
|
||||
val title: String,
|
||||
val coverImage: String,
|
||||
@@ -13,10 +16,11 @@ data class GetSeriesListResponse(
|
||||
val creator: SeriesListItemCreator,
|
||||
var numberOfContent: Int = 0,
|
||||
var isNew: Boolean = false,
|
||||
var isPopular: Boolean = false
|
||||
var isPopular: Boolean = false,
|
||||
val rawPublishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek> = emptySet()
|
||||
)
|
||||
|
||||
data class SeriesListItemCreator(
|
||||
data class SeriesListItemCreator @QueryProjection constructor(
|
||||
val creatorId: Long,
|
||||
val nickname: String,
|
||||
val profileImage: String
|
||||
|
||||
Reference in New Issue
Block a user