OriginalAudioDrama 리스트 조회 쿼리 최적화
OriginalAudioDrama 리스트 조회 시 엔티티 대신 DTO를 직접 조회하도록 개선 콘텐츠 개수, 신규 콘텐츠 여부, 번역 제목을 서브쿼리와 조인을 통해 한 번에 가져오도록 하여 기존의 N+1 문제와 다수의 추가 쿼리 발생을 해결
This commit is contained in:
@@ -131,15 +131,11 @@ class HomeService(
|
|||||||
isAdult = isAdult
|
isAdult = isAdult
|
||||||
)
|
)
|
||||||
|
|
||||||
// 오직 보이스온에서만
|
|
||||||
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType
|
||||||
orderByRandom = true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val translatedOriginalAudioDramaList = getTranslatedSeriesList(seriesList = originalAudioDramaList)
|
|
||||||
|
|
||||||
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
||||||
|
|
||||||
// 요일별 시리즈
|
// 요일별 시리즈
|
||||||
@@ -214,7 +210,7 @@ class HomeService(
|
|||||||
latestContentList = latestContentList,
|
latestContentList = latestContentList,
|
||||||
bannerList = bannerList,
|
bannerList = bannerList,
|
||||||
eventBannerList = eventBannerList,
|
eventBannerList = eventBannerList,
|
||||||
originalAudioDramaList = translatedOriginalAudioDramaList,
|
originalAudioDramaList = originalAudioDramaList,
|
||||||
auditionList = auditionList,
|
auditionList = auditionList,
|
||||||
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
||||||
popularCharacters = translatedPopularCharacters,
|
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.main.curation.QAudioContentCurationItem.audioContentCurationItem
|
||||||
import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse
|
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.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.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
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.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
|
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
|
||||||
|
|
||||||
@@ -64,12 +66,13 @@ interface ContentSeriesQueryRepository {
|
|||||||
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
|
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
|
||||||
fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series>
|
fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series>
|
||||||
fun getOriginalAudioDramaList(
|
fun getOriginalAudioDramaList(
|
||||||
|
imageHost: String,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
orderByRandom: Boolean = false,
|
locale: String,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 20
|
limit: Long = 20
|
||||||
): List<Series>
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
|
||||||
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
||||||
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
||||||
@@ -365,12 +368,13 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getOriginalAudioDramaList(
|
override fun getOriginalAudioDramaList(
|
||||||
|
imageHost: String,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
orderByRandom: Boolean,
|
locale: String,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long
|
||||||
): List<Series> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isOriginal.isTrue
|
var where = series.isOriginal.isTrue
|
||||||
.and(series.isActive.isTrue)
|
.and(series.isActive.isTrue)
|
||||||
|
|
||||||
@@ -392,20 +396,85 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
val now = LocalDateTime.now()
|
||||||
.selectFrom(series)
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
.innerJoin(series.member, member)
|
|
||||||
.where(where)
|
val contentCountSubquery = queryFactory
|
||||||
.orderBy(
|
.select(seriesContent.id.count())
|
||||||
if (orderByRandom) {
|
.from(seriesContent)
|
||||||
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
} else {
|
.where(
|
||||||
series.id.desc()
|
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)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.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 {
|
override fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int {
|
||||||
|
|||||||
@@ -55,12 +55,21 @@ class ContentSeriesService(
|
|||||||
fun getOriginalAudioDramaList(
|
fun getOriginalAudioDramaList(
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
orderByRandom: Boolean = false,
|
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 20
|
limit: Long = 20
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, contentType, orderByRandom, offset, limit)
|
val originalAudioDramaList = repository.getOriginalAudioDramaList(
|
||||||
return getTranslatedSeriesList(seriesToSeriesListItem(originalAudioDramaList, isAdult, contentType))
|
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> {
|
fun getGenreList(memberId: Long, isAdult: Boolean, contentType: ContentType): List<GetSeriesGenreListResponse> {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package kr.co.vividnext.sodalive.content.series
|
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(
|
data class GetSeriesListResponse(
|
||||||
val totalCount: Int,
|
val totalCount: Int,
|
||||||
val items: List<SeriesListItem>
|
val items: List<SeriesListItem>
|
||||||
) {
|
) {
|
||||||
data class SeriesListItem(
|
data class SeriesListItem @QueryProjection constructor(
|
||||||
val seriesId: Long,
|
val seriesId: Long,
|
||||||
val title: String,
|
val title: String,
|
||||||
val coverImage: String,
|
val coverImage: String,
|
||||||
@@ -13,10 +16,11 @@ data class GetSeriesListResponse(
|
|||||||
val creator: SeriesListItemCreator,
|
val creator: SeriesListItemCreator,
|
||||||
var numberOfContent: Int = 0,
|
var numberOfContent: Int = 0,
|
||||||
var isNew: Boolean = false,
|
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 creatorId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImage: String
|
val profileImage: String
|
||||||
|
|||||||
Reference in New Issue
Block a user