fix(osiv): lazy 관계 선로딩을 보완한다

This commit is contained in:
2026-06-20 00:05:48 +09:00
parent 92fe6caf17
commit 37ad325cc2
7 changed files with 532 additions and 14 deletions

View File

@@ -196,8 +196,12 @@ class AdminOriginalWorkService(
/** 원작 상세 조회 (소프트 삭제 제외) */
@Transactional(readOnly = true)
fun getOriginalWork(id: Long): OriginalWork {
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
val originalWork = originalWorkRepository.findByIdAndIsDeletedFalse(id)
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
initializeResponseRelations(originalWork)
return originalWork
}
/** 원작 페이징 조회 */
@@ -210,7 +214,9 @@ class AdminOriginalWorkService(
else -> size
}
val pageable = PageRequest.of(safePage, safeSize, Sort.by("createdAt").descending())
return originalWorkRepository.findByIsDeletedFalse(pageable)
val originalWorks = originalWorkRepository.findByIsDeletedFalse(pageable)
originalWorks.content.forEach { initializeResponseRelations(it) }
return originalWorks
}
/** 지정 원작에 속한 활성 캐릭터 페이징 조회 (최신순) */
@@ -233,7 +239,14 @@ class AdminOriginalWorkService(
/** 원작 검색 (제목/콘텐츠타입/카테고리, 소프트 삭제 제외) - 무페이징 */
@Transactional(readOnly = true)
fun searchOriginalWorksAll(searchTerm: String): List<OriginalWork> {
return originalWorkRepository.searchNoPaging(searchTerm)
val originalWorks = originalWorkRepository.searchNoPaging(searchTerm)
originalWorks.forEach { initializeResponseRelations(it) }
return originalWorks
}
private fun initializeResponseRelations(originalWork: OriginalWork) {
originalWork.originalLinks.forEach { it.url }
originalWork.tagMappings.forEach { it.tag.tag }
}
/** 원작에 기존 캐릭터들을 배정 */

View File

@@ -74,6 +74,28 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
pageable: Pageable
): List<ChatCharacter>
/**
* 특정 캐릭터와 태그를 공유하는 다른 캐릭터 ID를 무작위로 조회 (현재 캐릭터 제외)
*/
@Query(
"""
SELECT c.id FROM ChatCharacter c
JOIN c.tagMappings tm
JOIN tm.tag t
WHERE c.isActive = true
AND c.id <> :characterId
AND t.id IN (
SELECT t2.id FROM ChatCharacterTagMapping tm2 JOIN tm2.tag t2 WHERE tm2.chatCharacter.id = :characterId
)
GROUP BY c.id
ORDER BY function('RAND')
"""
)
fun findRandomIdsBySharedTags(
@Param("characterId") characterId: Long,
pageable: Pageable
): List<Long>
/**
* 활성 캐릭터 무작위 조회
*/
@@ -99,6 +121,27 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
fun findRandomActiveExcluding(@Param("excludeIds") excludeIds: List<Long>, pageable: Pageable): List<ChatCharacter>
fun findByIdInAndIsActiveTrue(ids: List<Long>): List<ChatCharacter>
@Query(
"""
SELECT DISTINCT c FROM ChatCharacter c
LEFT JOIN FETCH c.tagMappings tm
LEFT JOIN FETCH tm.tag
WHERE c.id = :id
"""
)
fun findByIdWithTagMappings(@Param("id") id: Long): ChatCharacter?
@Query(
"""
SELECT DISTINCT c FROM ChatCharacter c
LEFT JOIN FETCH c.tagMappings tm
LEFT JOIN FETCH tm.tag
WHERE c.id IN :ids
"""
)
fun findByIdInWithTagMappings(@Param("ids") ids: List<Long>): List<ChatCharacter>
fun findByCreatorMemberId(creatorMemberId: Long): ChatCharacter?
fun existsByCreatorMemberId(creatorMemberId: Long): Boolean
}

View File

@@ -210,13 +210,15 @@ class ChatCharacterService(
*/
@Transactional(readOnly = true)
fun getOtherCharactersBySharedTags(characterId: Long, limit: Int = 10): List<ChatCharacter> {
val others = chatCharacterRepository.findRandomBySharedTags(
val ids = chatCharacterRepository.findRandomIdsBySharedTags(
characterId,
PageRequest.of(0, limit)
)
// 태그 초기화 (지연 로딩 문제 방지)
others.forEach { it.tagMappings.size }
return others
).distinct()
if (ids.isEmpty()) return emptyList()
val charactersById = chatCharacterRepository.findByIdInWithTagMappings(ids)
.associateBy { it.id }
return ids.mapNotNull { charactersById[it] }
}
/**
@@ -555,13 +557,12 @@ class ChatCharacterService(
*/
@Transactional(readOnly = true)
fun getCharacterDetail(id: Long): ChatCharacter? {
val character = findById(id) ?: return null
val character = chatCharacterRepository.findByIdWithTagMappings(id) ?: return null
// 지연 로딩된 관계 데이터 초기화
character.tagMappings.size
character.valueMappings.size
character.hobbyMappings.size
character.goalMappings.size
character.valueMappings.forEach { it.value.value }
character.hobbyMappings.forEach { it.hobby.hobby }
character.goalMappings.forEach { it.goal.goal }
character.memories.size
character.personalities.size
character.backgrounds.size

View File

@@ -43,8 +43,13 @@ class OriginalWorkQueryService(
*/
@Transactional(readOnly = true)
fun getOriginalWork(id: Long): OriginalWork {
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
val originalWork = originalWorkRepository.findByIdAndIsDeletedFalse(id)
.orElseThrow { SodaException(messageKey = "chat.original.not_found") }
originalWork.originalLinks.forEach { it.url }
originalWork.tagMappings.forEach { it.tag.tag }
return originalWork
}
/**

View File

@@ -2,12 +2,14 @@ package kr.co.vividnext.sodalive.i18n.translation
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService.Companion.getTranslatableLanguageCodes
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class ResourceTranslationJobScheduler(
private val sourceExtractor: TranslationSourceExtractor,
private val translationJobScheduler: TranslationJobScheduler
) {
@Transactional
fun scheduleResourceTranslations(resourceType: LanguageTranslationTargetType, resourceId: Long) {
val source = sourceExtractor.extract(resourceType, resourceId) ?: return
getTranslatableLanguageCodes(source.sourceLanguage).forEach { targetLanguage ->
@@ -15,6 +17,7 @@ class ResourceTranslationJobScheduler(
}
}
@Transactional
fun scheduleResourceTranslation(
resourceType: LanguageTranslationTargetType,
resourceId: Long,

View File

@@ -23,6 +23,8 @@ import kr.co.vividnext.sodalive.member.Member
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 kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -53,6 +55,8 @@ class RankingRepository(
.select(member)
.from(creatorRanking)
.innerJoin(creatorRanking.member, member)
.leftJoin(member.tags, memberCreatorTag).fetchJoin()
.leftJoin(memberCreatorTag.tag, creatorTag).fetchJoin()
if (memberId != null) {
select = select.leftJoin(blockMember).on(blockMemberCondition)
@@ -65,6 +69,7 @@ class RankingRepository(
return select
.orderBy(creatorRanking.ranking.asc())
.fetch()
.distinctBy { it.id }
}
fun getAudioContentRanking(