5.1 KiB
5.1 KiB
PRD: 관리자 시리즈 배너 LazyInitializationException 수정
1. Overview
spring.jpa.open-in-view=false 환경에서 관리자 콘텐츠 시리즈 배너 목록 조회 시 SeriesBanner.series lazy proxy 접근으로 발생하는 LazyInitializationException을 방지한다.
2. Problem
AdminContentSeriesBannerController.getBannerList(...)는bannerService.getActiveBanners(...)로Page<SeriesBanner>를 받은 뒤 컨트롤러에서SeriesBannerResponse.from(...)으로 응답을 만든다.SeriesBanner.series는@ManyToOne(fetch = FetchType.LAZY)이다.SeriesBannerResponse.from(...)은banner.series.id,banner.series.title을 읽는다.spring.jpa.open-in-view=false환경에서는 서비스/리포지토리 조회 후 영속성 컨텍스트가 닫힌 상태에서 컨트롤러가 lazy proxy를 초기화하려 하므로org.hibernate.LazyInitializationException: could not initialize proxy [kr.co.vividnext.sodalive.creator.admin.content.series.Series#124] - no Session이 발생할 수 있다.
3. Goals
- OSIV off 환경에서도 관리자 콘텐츠 시리즈 배너 목록 조회가 예외 없이 응답된다.
- 사용자가 요청한 방향대로
ContentSeriesBannerService.getActiveBanners(...)안에서 관리자 목록 response를 생성한다. - 기존 관리자 콘텐츠 시리즈 배너 목록 API의 응답 스키마를 변경하지 않는다.
- 배너 언어 라벨을 시리즈 제목 뒤에 붙이는 기존 동작을 유지한다.
- 실패 재현 테스트를 먼저 작성하고, 최소 수정으로 통과시킨다.
4. Non-Goals
- OSIV 설정을 다시 켜지 않는다.
SeriesBanner.seriesfetch 전략을 전역 eager로 바꾸지 않는다.- 관리자 콘텐츠 시리즈 배너 목록 API의 URL, 요청 파라미터, 응답 필드를 변경하지 않는다.
- 배너 등록/수정/삭제/정렬 API 동작을 변경하지 않는다.
- 공개 사용자용 시리즈 배너 조회(
getDisplayBanners) 응답 구조를 변경하지 않는다. - QueryDSL/projection 기반으로 배너 조회 전체를 재설계하지 않는다.
5. Target Users
- 관리자: 관리자 화면에서 콘텐츠 시리즈 배너 목록을 조회하고 정렬/수정 대상을 확인하는 사용자
- 운영자: OSIV off 운영 환경에서도 관리자 시리즈 배너 목록이 안정적으로 열리기를 기대하는 사용자
6. User Stories
- 관리자는 시리즈가 연결된 활성 배너 목록을 조회할 때 서버 오류를 만나지 않아야 한다.
- 관리자는 한국어/영어/일본어 배너가 섞여 있어도 시리즈 제목 뒤에 언어 라벨이 붙은 목록을 확인할 수 있어야 한다.
- 운영자는 OSIV off 설정을 유지하면서 lazy 초기화 예외를 회피할 수 있어야 한다.
7. Core Features
Feature A. 관리자 시리즈 배너 목록 응답 생성 위치 이동
Requirements
ContentSeriesBannerService.getActiveBanners(...)는 관리자 목록 응답인SeriesBannerListPageResponse를 생성해 반환한다.ContentSeriesBannerService는 클래스 레벨@Transactional(readOnly = true)로 조회 기본 트랜잭션을 제공하고,getActiveBanners(...)는 그 경계 안에서 배너 조회와SeriesBannerResponse.from(...)변환을 완료한다.SeriesBannerResponse.from(...)호출 시appendLanguageToSeriesTitle = true를 유지한다.AdminContentSeriesBannerController.getBannerList(...)는 pageable 생성 후 서비스가 만든 response를 그대로ApiResponse.ok(...)로 감싼다.- 기존
SeriesBannerListPageResponse.totalCount,content[].id,content[].imagePath,content[].seriesId,content[].seriesTitle필드는 유지한다.
Edge Cases
- 활성 배너가 없으면
totalCount = 0,content = []를 반환한다. - 배너 언어가
KO,EN,JA인 경우 기존처럼 각각한국어,영어,일본어라벨을 시리즈 제목 뒤에 붙인다. - 이미지 경로 조합은 기존처럼
"$imageHost/${banner.imagePath}"형식을 유지한다.
8. Technical Constraints
- Kotlin + Spring Boot 2.7.14 + Java 17 + Spring Data JPA 기준으로 구현한다.
- 테스트 환경의
spring.jpa.open-in-view=false설정을 유지한다. ContentSeriesBannerService클래스 레벨에@Transactional(readOnly = true)를 사용하고, 기존 쓰기 메서드의 메서드 레벨@Transactional은 유지한다.- 변경 범위는 관리자 콘텐츠 시리즈 배너 목록 조회 흐름과 해당 테스트로 제한한다.
- lazy proxy 재현은 실제 JPA 환경에서 확인할 수 있도록 서비스 통합 테스트로 검증한다.
- 관리자 목록 API 응답은 mock 기반 컨트롤러 테스트가 아니라 실제 Spring Context,
MockMvc, JPA fixture를 연결한 통합 테스트로 검증한다.
9. Metrics
ContentSeriesBannerServiceIntegrationTest에서 OSIV off 조건의 관리자 시리즈 배너 목록 응답 생성 테스트가 통과한다.AdminContentSeriesBannerControllerIntegrationTest의 관리자 시리즈 배너 목록 API 테스트가 통과한다.- 관련 단일 테스트와
ktlintCheck가 통과한다.