Files

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.series fetch 전략을 전역 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가 통과한다.