7.4 KiB
7.4 KiB
관리자 회원 목록 LazyInitializationException 수정 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:executing-plans또는 동등한 TDD 절차로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.
Goal: spring.jpa.open-in-view=false 환경에서 관리자 회원 리스트와 크리에이터 리스트 조회가 Member.signOutReasons lazy collection 접근 때문에 실패하지 않게 한다.
Architecture: 기존 AdminMemberService의 응답 매핑 구조는 유지한다. 서비스 클래스에 read-only 트랜잭션을 기본 적용해 목록 조회와 응답 매핑 전체를 열린 영속성 컨텍스트 안에서 처리한다. 쓰기 메서드는 기존 메서드 레벨 @Transactional로 read-only 기본값을 override한다.
Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring Data JPA, QueryDSL, JUnit 5, Gradle Wrapper
0. 구현 전 확정 사항
- API 응답 스키마는 변경하지 않는다.
Member.signOutReasons를 eager로 바꾸지 않는다.- OSIV 설정을 켜지 않는다.
- 리포지토리 fetch join이나 projection 전면 개편은 이번 범위에서 제외한다.
- lazy 접근 문제가 확인된 대상 메서드:
AdminMemberService.getMemberList(...)AdminMemberService.searchMember(...)AdminMemberService.getCreatorList(...)AdminMemberService.searchCreator(...)
1. 파일 구조 계획
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt- 클래스 레벨에
@Transactional(readOnly = true)를 추가하고, 기존 쓰기 메서드의@Transactional은 유지한다.
- 클래스 레벨에
- Create:
src/test/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberServiceTest.kt- OSIV off 환경에서 탈퇴 이력이 있는 회원/크리에이터 목록 조회가 예외 없이 응답되는지 검증한다.
- Verify:
src/test/resources/application.ymlspring.jpa.open-in-view: false테스트 설정을 그대로 사용한다.
Phase 1: LazyInitializationException 재현 테스트
- Task 1.1: 관리자 회원/크리에이터 목록 실패 테스트 작성
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberServiceTest.kt - RED:
@SpringBootTest(properties = ["cloud.aws.cloud-front.host=https://cdn.test"])통합 테스트를 추가한다. - RED: 테스트 클래스에는
@Transactional을 붙이지 않아 서비스 호출이 테스트 트랜잭션에 의해 가려지지 않게 한다. - RED:
MemberRole.USER회원과MemberRole.CREATOR회원을 저장하고, 각각SignOut을 저장한다. - RED:
service.getMemberList(PageRequest.of(0, 20)),service.getCreatorList(PageRequest.of(0, 20))를 호출해signOutDate가 비어 있지 않고 예외가 발생하지 않기를 기대한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest - 기대 결과: production code 수정 전에는
LazyInitializationException으로 테스트가 실패한다. - 구현 기록(2026-06-27):
AdminMemberServiceTest를 추가해@Transactional없는 테스트 클래스에서 서비스 목록 조회를 호출하도록 했다.- 1차 RED:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest실행 시 Redis 연결 실패로 Spring context 생성이 실패해 의도한 실패가 아니었다. - 보정: 기존 통합 테스트 패턴에 맞춰
EmbeddedRedisInitializer를 추가했다. - 2차 RED: 같은 명령 재실행 결과
getMemberList,getCreatorList모두LazyInitializationException으로 실패해 OSIV off lazy collection 접근 문제를 재현했다.
- 1차 RED:
- Test:
Phase 2: 서비스 read-only 트랜잭션 보강
- Task 2.1: 서비스 클래스에 read-only 트랜잭션 기본값 추가
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt - GREEN:
AdminMemberService클래스에@Transactional(readOnly = true)를 추가한다. - GREEN:
updateMember,resetPassword의 기존 메서드 레벨@Transactional은 유지해 쓰기 트랜잭션으로 동작하게 한다. - GREEN: 응답 매핑 로직과 리포지토리 쿼리는 변경하지 않는다.
- 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest - 기대 결과:
BUILD SUCCESSFUL - REFACTOR: 불필요한 import/format 변경이 생기지 않았는지 확인한다.
- 구현 기록(2026-06-27): 최초 구현에서는
getMemberList,searchMember,getCreatorList,searchCreator에@Transactional(readOnly = true)를 추가했다.- GREEN:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest실행 결과BUILD SUCCESSFUL을 확인했다. - 검증 이유: OSIV off 환경에서 서비스 메서드의 read-only 트랜잭션 안에서
signOutReasons와authlazy 접근이 완료되는지 확인했다.
- GREEN:
- 후속 수정(2026-06-27): 리뷰 피드백에 따라 개별 조회 메서드 annotation을 제거하고
AdminMemberService클래스 레벨@Transactional(readOnly = true)로 정리했다. 쓰기 메서드updateMember,resetPassword는 기존 메서드 레벨@Transactional을 유지했다.
- Modify:
Phase 3: 회귀 검증과 문서 기록
- Task 3.1: 관련 검증 실행 및 문서 기록
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest - Verify:
./gradlew :app:ktlintCheck는 단일 루트 프로젝트에:app모듈이 없으면 실행하지 않고./gradlew ktlintCheck로 대체한다. - Verify:
./gradlew ktlintCheck - Verify:
./gradlew tasks --all - 문서 기록: 각 task 아래에 실행 명령, 결과, 검증 이유를 한국어로 누적한다.
- 구현 기록(2026-06-27): 관련 단일 테스트, ktlint, Gradle task 목록 검증을 실행했다.
- 단일 테스트:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest실행 결과BUILD SUCCESSFUL을 확인했다. - ktlint 1차:
./gradlew ktlintCheck를./gradlew tasks --all과 동시에 실행했을 때~/.gradlewrapper lock 파일 접근 sandbox 오류로 실패했다. - ktlint 재실행:
./gradlew --no-daemon ktlintCheck실행 결과BUILD SUCCESSFUL을 확인했다. - 명령 유효성:
./gradlew --no-daemon tasks --all실행 결과BUILD SUCCESSFUL을 확인했다.
- 단일 테스트:
- Verify:
검증 기록
- 2026-06-27:
./gradlew test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest로 OSIV off lazy collection 재현 테스트가 수정 후 통과함을 확인했다. - 2026-06-27:
./gradlew --no-daemon ktlintCheck로 Kotlin formatting 검증이 통과함을 확인했다. - 2026-06-27:
./gradlew --no-daemon tasks --all로 문서에 안내된 Gradle 명령 목록이 유효함을 확인했다. - 2026-06-27: 최종 확인으로
./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest와./gradlew --no-daemon ktlintCheck를 재실행했고 둘 다BUILD SUCCESSFUL을 확인했다. - 2026-06-27: 클래스 레벨
@Transactional(readOnly = true)후속 변경 후./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.admin.member.AdminMemberServiceTest와./gradlew --no-daemon ktlintCheck를 재실행했고 둘 다BUILD SUCCESSFUL을 확인했다.