# PRD: 관리자 회원 목록 LazyInitializationException 수정 ## 1. Overview `spring.jpa.open-in-view=false` 환경에서 관리자 회원 리스트와 크리에이터 리스트 조회 시 `Member.signOutReasons` lazy collection 접근으로 발생하는 `LazyInitializationException`을 방지한다. --- ## 2. Problem - 관리자 회원 목록 응답 생성 중 `Member.signOutReasons`를 읽어 탈퇴일을 계산한다. - 현재 `AdminMemberService`의 목록 조회 메서드는 트랜잭션 경계가 없어 QueryDSL 조회 후 영속성 컨텍스트가 닫힌 상태에서 lazy collection을 접근할 수 있다. - `spring.jpa.open-in-view=false` 환경에서는 이 접근이 `org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: kr.co.vividnext.sodalive.member.Member.signOutReasons`로 이어진다. - 같은 응답 매핑 흐름을 사용하는 관리자 회원 리스트, 회원 검색, 크리에이터 리스트, 크리에이터 검색 모두 같은 위험이 있다. --- ## 3. Goals - `osiv=false` 환경에서도 관리자 회원 리스트와 크리에이터 리스트가 예외 없이 응답된다. - 기존 API 응답 스키마와 정렬/필터 조건을 변경하지 않는다. - 탈퇴 이력이 있는 회원의 `signOutDate` 계산 동작을 유지한다. - 서비스 계층에 명확한 read-only 트랜잭션 기본 경계를 둔다. - 실패 재현 테스트를 먼저 작성하고, 최소 수정으로 통과시킨다. --- ## 4. Non-Goals - 관리자 회원 목록 API의 응답 필드를 변경하지 않는다. - `Member.signOutReasons` fetch 전략을 전역 eager로 바꾸지 않는다. - 목록 조회를 projection 전용 쿼리로 전면 개편하지 않는다. - pagination/count 쿼리 구조를 리팩터링하지 않는다. - OSIV 설정을 다시 켜지 않는다. --- ## 5. Target Users - 관리자: 관리자 화면에서 회원 목록과 크리에이터 목록을 조회하는 사용자 - 운영자: 탈퇴 또는 차단 이력이 있는 회원을 포함한 목록을 안정적으로 확인해야 하는 사용자 --- ## 6. User Stories - 관리자는 탈퇴 이력이 있는 일반 회원이 포함된 회원 리스트를 조회해도 서버 오류를 만나지 않아야 한다. - 관리자는 탈퇴 이력이 있는 크리에이터가 포함된 크리에이터 리스트를 조회해도 서버 오류를 만나지 않아야 한다. - 관리자는 검색 결과에서도 동일하게 탈퇴일과 활성 상태를 확인할 수 있어야 한다. --- ## 7. Core Features ### Feature A. 관리자 회원 목록 조회 트랜잭션 보강 #### Requirements - `AdminMemberService`는 클래스 레벨 `@Transactional(readOnly = true)`로 조회 기본 트랜잭션을 제공한다. - `AdminMemberService.getMemberList(...)`는 read-only 트랜잭션 안에서 조회와 응답 매핑을 완료한다. - `AdminMemberService.searchMember(...)`는 read-only 트랜잭션 안에서 조회와 응답 매핑을 완료한다. - `AdminMemberService.getCreatorList(...)`는 read-only 트랜잭션 안에서 조회와 응답 매핑을 완료한다. - `AdminMemberService.searchCreator(...)`는 read-only 트랜잭션 안에서 조회와 응답 매핑을 완료한다. - `AdminMemberService.updateMember(...)`, `AdminMemberService.resetPassword(...)`는 메서드 레벨 `@Transactional`로 쓰기 트랜잭션을 유지한다. - 기존 `processMemberListToGetAdminMemberListResponseItemList(...)`의 응답 필드 계산 방식은 유지한다. #### Edge Cases - `signOutReasons`가 비어 있으면 기존처럼 `signOutDate`는 빈 문자열이다. - `signOutReasons`가 있으면 기존처럼 마지막 탈퇴 이력의 `createdAt`을 KST `yyyy-MM-dd HH:mm` 형식으로 내려준다. - `auth` lazy one-to-one 접근도 같은 read-only 트랜잭션 안에서 처리되어야 한다. --- ## 8. Technical Constraints - Kotlin + Spring Boot 2.7.14 + Spring Data JPA 기준으로 구현한다. - 테스트 환경의 `spring.jpa.open-in-view=false` 설정을 유지한다. - 서비스 클래스에는 `@Transactional(readOnly = true)`를 사용하고, 쓰기 메서드는 기존 메서드 레벨 `@Transactional`을 유지한다. - 변경 범위는 `AdminMemberService`와 해당 테스트로 제한한다. --- ## 9. Metrics - `AdminMemberServiceTest`에서 OSIV off 조건의 회원/크리에이터 목록 조회 테스트가 통과한다. - 관련 단일 테스트와 `ktlintCheck`가 통과한다.