From 1b039bccea1d90d12460b61b5b804816d4939936 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 13 Feb 2026 17:49:31 +0900 Subject: [PATCH] =?UTF-8?q?Group=5Fconcat=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=A0=88=EB=B2=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B3=91?= =?UTF-8?q?=ED=95=A9=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EnumPath 사용 시 발생하는 Hibernate QueryException을 해결하기 위해 group_concat 사용을 전면 제거함. 연재 요일 데이터를 개별 쿼리로 조회한 후 메모리에서 시리즈 ID를 기준으로 그룹화하여 결과를 생성하도록 수정함. --- .../content/series/ContentSeriesRepository.kt | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt index e9123360..f9995040 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -114,24 +114,6 @@ interface ContentSeriesQueryRepository { class ContentSeriesQueryRepositoryImpl( private val queryFactory: JPAQueryFactory ) : ContentSeriesQueryRepository { - private fun parsePublishedDaysOfWeek(raw: String?): Set { - 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( creatorId: Long?, isAuth: Boolean, @@ -275,11 +257,6 @@ class ContentSeriesQueryRepositoryImpl( audioContent.releaseDate.between(sevenDaysAgo, now) ) 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 .select( @@ -292,14 +269,12 @@ class ContentSeriesQueryRepositoryImpl( member.nickname, member.profileImage, contentCount, - isNewFlag, - publishedDaysConcat + isNewFlag ) .from(series) .innerJoin(series.member, member) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent.content, audioContent) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) .groupBy(series.id) @@ -309,6 +284,16 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .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 -> val sId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -321,7 +306,7 @@ class ContentSeriesQueryRepositoryImpl( val profImg = row.get(member.profileImage) val nContent = row.get(contentCount) ?: 0L val isN = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = sId, @@ -336,7 +321,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = nContent.toInt(), isNew = isN, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } } @@ -451,11 +436,6 @@ class ContentSeriesQueryRepositoryImpl( audioContent.releaseDate.between(sevenDaysAgo, now) ) 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 results = queryFactory @@ -469,15 +449,13 @@ class ContentSeriesQueryRepositoryImpl( member.nickname, member.profileImage, contentCount, - isNewFlag, - publishedDaysConcat + isNewFlag ) .from(seriesContent) .innerJoin(seriesContent.series, series) .innerJoin(seriesContent.content, audioContent) .innerJoin(series.member, member) .innerJoin(series.genre, seriesGenre) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) .groupBy(series.id) @@ -487,6 +465,16 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .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 -> val sId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -499,7 +487,7 @@ class ContentSeriesQueryRepositoryImpl( val profImg = row.get(member.profileImage) val nContent = row.get(contentCount) ?: 0L val isN = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = sId, @@ -514,7 +502,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = nContent.toInt(), isNew = isN, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } } @@ -623,11 +611,6 @@ class ContentSeriesQueryRepositoryImpl( audioContent.releaseDate.between(sevenDaysAgo, now) ) 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 .select( @@ -640,14 +623,12 @@ class ContentSeriesQueryRepositoryImpl( member.nickname, member.profileImage, contentCount, - isNewFlag, - publishedDaysConcat + isNewFlag ) .from(series) .innerJoin(series.member, member) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent.content, audioContent) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) .groupBy(series.id) @@ -657,6 +638,16 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .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 -> val sId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -669,7 +660,7 @@ class ContentSeriesQueryRepositoryImpl( val profImg = row.get(member.profileImage) val nContent = row.get(contentCount) ?: 0L val isN = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = sId, @@ -684,7 +675,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = nContent.toInt(), isNew = isN, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } } @@ -733,11 +724,6 @@ class ContentSeriesQueryRepositoryImpl( audioContent.releaseDate.between(sevenDaysAgo, now) ) 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 .select( @@ -750,14 +736,12 @@ class ContentSeriesQueryRepositoryImpl( member.nickname, member.profileImage, contentCount, - isNewFlag, - publishedDaysConcat + isNewFlag ) .from(series) .innerJoin(series.member, member) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent.content, audioContent) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) .groupBy(series.id) @@ -767,6 +751,16 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .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 -> val seriesId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -779,7 +773,7 @@ class ContentSeriesQueryRepositoryImpl( val profileImage = row.get(member.profileImage) val numberOfContent = row.get(contentCount) ?: 0L val isNew = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[seriesId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = seriesId, @@ -794,7 +788,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = numberOfContent.toInt(), isNew = isNew, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } } @@ -931,11 +925,6 @@ class ContentSeriesQueryRepositoryImpl( ) val isNewFlag = isNewCase.max() 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 .select( @@ -949,7 +938,6 @@ class ContentSeriesQueryRepositoryImpl( member.profileImage, contentCount, isNewFlag, - publishedDaysConcat, minCurationOrder ) .from(audioContentCurationItem) @@ -958,7 +946,6 @@ class ContentSeriesQueryRepositoryImpl( .innerJoin(series.member, member) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent.content, audioContent) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(blockMember).on(blockMemberCondition) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) @@ -966,6 +953,16 @@ class ContentSeriesQueryRepositoryImpl( .orderBy(minCurationOrder.asc(), series.id.asc()) .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 -> val sId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -978,7 +975,7 @@ class ContentSeriesQueryRepositoryImpl( val profImg = row.get(member.profileImage) val nContent = row.get(contentCount) ?: 0L val isN = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = sId, @@ -993,7 +990,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = nContent.toInt(), isNew = isN, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } } @@ -1053,11 +1050,6 @@ class ContentSeriesQueryRepositoryImpl( audioContent.releaseDate.between(sevenDaysAgo, now) ) 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 .select( @@ -1070,14 +1062,12 @@ class ContentSeriesQueryRepositoryImpl( member.nickname, member.profileImage, contentCount, - isNewFlag, - publishedDaysConcat + isNewFlag ) .from(series) .innerJoin(series.member, member) .innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id)) .innerJoin(seriesContent.content, audioContent) - .leftJoin(series.publishedDaysOfWeek, seriesPublishedDay) .leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale)) .where(where) .groupBy(series.id) @@ -1087,6 +1077,16 @@ class ContentSeriesQueryRepositoryImpl( .orderBy(series.createdAt.desc()) .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 -> val sId = row.get(series.id)!! val originTitle = row.get(series.title)!! @@ -1099,7 +1099,7 @@ class ContentSeriesQueryRepositoryImpl( val profImg = row.get(member.profileImage) val nContent = row.get(contentCount) ?: 0L val isN = (row.get(isNewFlag) ?: 0) > 0 - val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat)) + val rawDays = daysMap[sId]?.filterNotNull()?.toSet() ?: emptySet() GetSeriesListResponse.SeriesListItem( seriesId = sId, @@ -1114,7 +1114,7 @@ class ContentSeriesQueryRepositoryImpl( ), numberOfContent = nContent.toInt(), isNew = isN, - rawPublishedDaysOfWeek = rawDays + rawPublishedDaysOfWeek = rawDays.sortedBy { it.ordinal }.toSet() ) } }