Group_concat 제거 및 애플리케이션 레벨 데이터 병합 적용

EnumPath 사용 시 발생하는 Hibernate QueryException을 해결하기 위해 group_concat 사용을 전면 제거함.
연재 요일 데이터를 개별 쿼리로 조회한 후 메모리에서 시리즈 ID를 기준으로 그룹화하여 결과를 생성하도록 수정함.
This commit is contained in:
2026-02-13 17:49:31 +09:00
parent a76c3ba34a
commit 1b039bccea

View File

@@ -114,24 +114,6 @@ interface ContentSeriesQueryRepository {
class ContentSeriesQueryRepositoryImpl( class ContentSeriesQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory private val queryFactory: JPAQueryFactory
) : ContentSeriesQueryRepository { ) : ContentSeriesQueryRepository {
private fun parsePublishedDaysOfWeek(raw: String?): Set<SeriesPublishedDaysOfWeek> {
if (raw.isNullOrBlank()) {
return emptySet()
}
return raw.split(",")
.mapNotNull { value ->
val trimmed = value.trim()
if (trimmed.isEmpty()) {
null
} else {
runCatching { SeriesPublishedDaysOfWeek.valueOf(trimmed) }.getOrNull()
}
}
.sortedBy { it.ordinal }
.toCollection(LinkedHashSet())
}
override fun getSeriesTotalCount( override fun getSeriesTotalCount(
creatorId: Long?, creatorId: Long?,
isAuth: Boolean, isAuth: Boolean,
@@ -275,11 +257,6 @@ class ContentSeriesQueryRepositoryImpl(
audioContent.releaseDate.between(sevenDaysAgo, now) audioContent.releaseDate.between(sevenDaysAgo, now)
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val results = queryFactory val results = queryFactory
.select( .select(
@@ -292,14 +269,12 @@ class ContentSeriesQueryRepositoryImpl(
member.nickname, member.nickname,
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag
publishedDaysConcat
) )
.from(series) .from(series)
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
@@ -309,6 +284,16 @@ class ContentSeriesQueryRepositoryImpl(
.limit(limit) .limit(limit)
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val sId = row.get(series.id)!! val sId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -321,7 +306,7 @@ class ContentSeriesQueryRepositoryImpl(
val profImg = row.get(member.profileImage) val profImg = row.get(member.profileImage)
val nContent = row.get(contentCount) ?: 0L val nContent = row.get(contentCount) ?: 0L
val isN = (row.get(isNewFlag) ?: 0) > 0 val isN = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = sId, seriesId = sId,
@@ -336,7 +321,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = nContent.toInt(), numberOfContent = nContent.toInt(),
isNew = isN, isNew = isN,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }
@@ -451,11 +436,6 @@ class ContentSeriesQueryRepositoryImpl(
audioContent.releaseDate.between(sevenDaysAgo, now) audioContent.releaseDate.between(sevenDaysAgo, now)
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val latestReleaseDate = audioContent.releaseDate.max() val latestReleaseDate = audioContent.releaseDate.max()
val results = queryFactory val results = queryFactory
@@ -469,15 +449,13 @@ class ContentSeriesQueryRepositoryImpl(
member.nickname, member.nickname,
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag
publishedDaysConcat
) )
.from(seriesContent) .from(seriesContent)
.innerJoin(seriesContent.series, series) .innerJoin(seriesContent.series, series)
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(series.genre, seriesGenre) .innerJoin(series.genre, seriesGenre)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
@@ -487,6 +465,16 @@ class ContentSeriesQueryRepositoryImpl(
.limit(limit) .limit(limit)
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val sId = row.get(series.id)!! val sId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -499,7 +487,7 @@ class ContentSeriesQueryRepositoryImpl(
val profImg = row.get(member.profileImage) val profImg = row.get(member.profileImage)
val nContent = row.get(contentCount) ?: 0L val nContent = row.get(contentCount) ?: 0L
val isN = (row.get(isNewFlag) ?: 0) > 0 val isN = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = sId, seriesId = sId,
@@ -514,7 +502,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = nContent.toInt(), numberOfContent = nContent.toInt(),
isNew = isN, isNew = isN,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }
@@ -623,11 +611,6 @@ class ContentSeriesQueryRepositoryImpl(
audioContent.releaseDate.between(sevenDaysAgo, now) audioContent.releaseDate.between(sevenDaysAgo, now)
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val results = queryFactory val results = queryFactory
.select( .select(
@@ -640,14 +623,12 @@ class ContentSeriesQueryRepositoryImpl(
member.nickname, member.nickname,
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag
publishedDaysConcat
) )
.from(series) .from(series)
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
@@ -657,6 +638,16 @@ class ContentSeriesQueryRepositoryImpl(
.limit(limit) .limit(limit)
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val sId = row.get(series.id)!! val sId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -669,7 +660,7 @@ class ContentSeriesQueryRepositoryImpl(
val profImg = row.get(member.profileImage) val profImg = row.get(member.profileImage)
val nContent = row.get(contentCount) ?: 0L val nContent = row.get(contentCount) ?: 0L
val isN = (row.get(isNewFlag) ?: 0) > 0 val isN = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = sId, seriesId = sId,
@@ -684,7 +675,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = nContent.toInt(), numberOfContent = nContent.toInt(),
isNew = isN, isNew = isN,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }
@@ -733,11 +724,6 @@ class ContentSeriesQueryRepositoryImpl(
audioContent.releaseDate.between(sevenDaysAgo, now) audioContent.releaseDate.between(sevenDaysAgo, now)
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val results = queryFactory val results = queryFactory
.select( .select(
@@ -750,14 +736,12 @@ class ContentSeriesQueryRepositoryImpl(
member.nickname, member.nickname,
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag
publishedDaysConcat
) )
.from(series) .from(series)
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
@@ -767,6 +751,16 @@ class ContentSeriesQueryRepositoryImpl(
.limit(limit) .limit(limit)
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val seriesId = row.get(series.id)!! val seriesId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -779,7 +773,7 @@ class ContentSeriesQueryRepositoryImpl(
val profileImage = row.get(member.profileImage) val profileImage = row.get(member.profileImage)
val numberOfContent = row.get(contentCount) ?: 0L val numberOfContent = row.get(contentCount) ?: 0L
val isNew = (row.get(isNewFlag) ?: 0) > 0 val isNew = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[seriesId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = seriesId, seriesId = seriesId,
@@ -794,7 +788,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = numberOfContent.toInt(), numberOfContent = numberOfContent.toInt(),
isNew = isNew, isNew = isNew,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }
@@ -931,11 +925,6 @@ class ContentSeriesQueryRepositoryImpl(
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val minCurationOrder = audioContentCurationItem.orders.min() val minCurationOrder = audioContentCurationItem.orders.min()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val results = queryFactory val results = queryFactory
.select( .select(
@@ -949,7 +938,6 @@ class ContentSeriesQueryRepositoryImpl(
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag,
publishedDaysConcat,
minCurationOrder minCurationOrder
) )
.from(audioContentCurationItem) .from(audioContentCurationItem)
@@ -958,7 +946,6 @@ class ContentSeriesQueryRepositoryImpl(
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(blockMember).on(blockMemberCondition) .leftJoin(blockMember).on(blockMemberCondition)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
@@ -966,6 +953,16 @@ class ContentSeriesQueryRepositoryImpl(
.orderBy(minCurationOrder.asc(), series.id.asc()) .orderBy(minCurationOrder.asc(), series.id.asc())
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val sId = row.get(series.id)!! val sId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -978,7 +975,7 @@ class ContentSeriesQueryRepositoryImpl(
val profImg = row.get(member.profileImage) val profImg = row.get(member.profileImage)
val nContent = row.get(contentCount) ?: 0L val nContent = row.get(contentCount) ?: 0L
val isN = (row.get(isNewFlag) ?: 0) > 0 val isN = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = sId, seriesId = sId,
@@ -993,7 +990,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = nContent.toInt(), numberOfContent = nContent.toInt(),
isNew = isN, isNew = isN,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }
@@ -1053,11 +1050,6 @@ class ContentSeriesQueryRepositoryImpl(
audioContent.releaseDate.between(sevenDaysAgo, now) audioContent.releaseDate.between(sevenDaysAgo, now)
) )
val isNewFlag = isNewCase.max() val isNewFlag = isNewCase.max()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val publishedDaysConcat = Expressions.stringTemplate(
"function('group_concat', {0}.stringValue())",
seriesPublishedDay
)
val results = queryFactory val results = queryFactory
.select( .select(
@@ -1070,14 +1062,12 @@ class ContentSeriesQueryRepositoryImpl(
member.nickname, member.nickname,
member.profileImage, member.profileImage,
contentCount, contentCount,
isNewFlag, isNewFlag
publishedDaysConcat
) )
.from(series) .from(series)
.innerJoin(series.member, member) .innerJoin(series.member, member)
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
.innerJoin(seriesContent.content, audioContent) .innerJoin(seriesContent.content, audioContent)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
@@ -1087,6 +1077,16 @@ class ContentSeriesQueryRepositoryImpl(
.orderBy(series.createdAt.desc()) .orderBy(series.createdAt.desc())
.fetch() .fetch()
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
val seriesIds = results.map { it.get(series.id)!! }
val daysMap = queryFactory
.select(series.id, seriesPublishedDay)
.from(series)
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
.where(series.id.`in`(seriesIds))
.fetch()
.groupBy({ it.get(series.id)!! }, { it.get(seriesPublishedDay) })
return results.map { row -> return results.map { row ->
val sId = row.get(series.id)!! val sId = row.get(series.id)!!
val originTitle = row.get(series.title)!! val originTitle = row.get(series.title)!!
@@ -1099,7 +1099,7 @@ class ContentSeriesQueryRepositoryImpl(
val profImg = row.get(member.profileImage) val profImg = row.get(member.profileImage)
val nContent = row.get(contentCount) ?: 0L val nContent = row.get(contentCount) ?: 0L
val isN = (row.get(isNewFlag) ?: 0) > 0 val isN = (row.get(isNewFlag) ?: 0) > 0
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet()
GetSeriesListResponse.SeriesListItem( GetSeriesListResponse.SeriesListItem(
seriesId = sId, seriesId = sId,
@@ -1114,7 +1114,7 @@ class ContentSeriesQueryRepositoryImpl(
), ),
numberOfContent = nContent.toInt(), numberOfContent = nContent.toInt(),
isNew = isN, isNew = isN,
rawPublishedDaysOfWeek = rawDays rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet()
) )
} }
} }