[프로젝트] Lazy Loading으로 인한 LazyInitializationException

silver's avatar
Jan 06, 2025
[프로젝트] Lazy Loading으로 인한 LazyInitializationException

발생한 에러

💡
ERROR 52483 --- [exercise-app-server] [nio-8080-exec-2] c.m.e._.error.GeneralExceptionHandler : Could not initialize proxy [com.metacoding.exerciseappserver.category.Category#1] - no session

원인 분석

에러는 Hibernate/JPA의 Lazy Loading 전략과 관련된 에러

왜 발생했는가?

  1. JPA에서 연관관계 데이터는 기본적으로 Lazy Loading(지연 로딩) 방식으로 동작
  1. 서비스 레이어에서 트랜잭션이 종료된 후, 컨트롤러나 뷰 영역에서 Lazy Loading된 객체에 접근 시도
  1. 세션이 이미 닫힌 상태이므로 프록시 객체를 초기화할 수 없어 예외 발생

해결 방법

Service 레이어에 @Transactional(readOnly = true) 적용

왜 이 방법으로 해결되는가?

  • @Transactional 어노테이션이 메서드 실행 동안 영속성 컨텍스트(세션)를 유지
  • Lazy Loading이 필요한 연관 엔티티에 접근할 때도 세션이 살아있어 정상적으로 데이터 로드 가능

@Transactional(readOnly = true)를 사용하지 않을 경우의 문제점들

1. 성능 저하

  • 데이터베이스가 읽기 전용 트랜잭션을 최적화할 수 없음
  • 다중 사용자 환경에서 불필요한 잠금(Lock)으로 인한 성능 저하

2. 코드 의도 불명확

  • 해당 메서드가 데이터를 수정하지 않는다는 의도를 명확히 표현하지 못함
  • 유지보수 시 다른 개발자의 혼란 유발 가능

3. 데이터 무결성 위험

  • 실수로 데이터 수정 쿼리가 실행될 가능성
  • 의도하지 않은 데이터 변경으로 인한 무결성 훼손 위험

4. 예상치 못한 오류

  • 읽기 전용 데이터베이스 환경에서 수정 작업 시도 시 실패
  • 추가적인 예외 처리 필요

5. 복잡한 트랜잭션 관리

  • 읽기/쓰기 혼합 트랜잭션에서 관리 복잡도 증가
  • 트랜잭션 격리 수준에 따른 일관성 문제 발생 가능

다른 해결방법

1. Fetch Join 사용

@Query("SELECT e FROM Exercise e JOIN FETCH e.category") List<Exercise> findAllWithCategory();
 

2. @EntityGraph 사용

@EntityGraph(attributePaths = {"category"}) List<Exercise> findAll();
 

3. DTO 변환 시점 조정

@Transactional(readOnly = true) public List<ExerciseDTO> getExerciseList() { List<Exercise> exercises = exerciseRepository.findAll(); // 트랜잭션 내에서 DTO 변환 (Lazy Loading 발생) return exercises.stream() .map(ExerciseDTO::from) .collect(Collectors.toList()); }

🎯 결론

읽기 전용 작업에는 반드시 @Transactional(readOnly = true)를 사용하자!
  • 성능 최적화
  • 코드 가독성 향상
  • 데이터 무결성 보장
  • LazyInitializationException 예방
Share article

silver