발생한 에러
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 전략과 관련된 에러
왜 발생했는가?
- JPA에서 연관관계 데이터는 기본적으로 Lazy Loading(지연 로딩) 방식으로 동작
- 서비스 레이어에서 트랜잭션이 종료된 후, 컨트롤러나 뷰 영역에서 Lazy Loading된 객체에 접근 시도
- 세션이 이미 닫힌 상태이므로 프록시 객체를 초기화할 수 없어 예외 발생
해결 방법
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