JPA를 사용하다보면 한 번 쯤은 마주치게 되는 지연로딩으로 인한 예외입니다.
워낙 유명한 이슈이고, 여러 해결방법들도 어렵지 않게 찾아볼 수 있습니다.
하지만 유명한 이슈라는 건 많은 사람들이 JPA, 지연로딩의 정확한 의미와 사용을 잘 모르기 때문이 아닐까 생각해봅니다..
저는 평소에도 해당 이슈를 접할 때마다 구글링을 통해 해결했지만,
이번 N+1 문제를 공부하며 확실하게 정리해보고자 적게 되었습니다.
일반적인 백엔드 프로젝트에서 로직의 대부분은 REST API에 해당하고, 실행의 흐름은 Controller → Service → Repository 순서입니다
Service 레벨에서 @Transactional이 명시된 메소드가 종료되면 Hibernate의 Session(Transaction)도 함께 종료됩니다.
이러한 경우, FetchType.LAZY 가 설정된 필드가 포함된 Entity에 대해 Controller 레벨에서 해당 필드를 조회하게 되면 Session은 이미 종료되었기 때문에 LazyInitializationException 이 발생합니다.
즉, 요약하자면 지연로딩 사용 시 해당 필드 값은 Session 동안 프록시 객체로 채운 후, 실제 필드를 조회하는 경우에만 프록시를 실제 DB 값을 불러와 채우게 됩니다.
하지만 채우지 않고 빈 프록시 객체인 상태로 Session이 종료되면, Getter를 사용해도 실제 값으로 채우지 못하게 되어(프록시 초기화 불가) 해당 오류가 발생하는 것이죠
(잘 이해가 되지 않는다면 프록시와 즉시로딩, 지연로딩 개념을 공부하는 걸 추천합니다:) )
그렇다면 어떠한 해결방법들이 있을까요?
이 방법을 써! 처럼 명확한 해결 방법은 없습니다.
가장 많이 쓰이는 방법은 Service단에서 미리 필요한 데이터를 Session안에서 불러와 저장 후 Controller단으로 전달하는 방법입니다.
흔히 말하는 DTO 를 사용하는 방법이죠 (저 역시도 이 방법을 사용합니다)
현재는 가장 이상적인 해결방법인 것 같습니다.
OSIV 란 JPA의 영속성 컨텍스트와 Hibernate의 Session(Transaction)을 View가 렌더링 될 때까지 유지하는 방법입니다.
//application.properties
spring.jpa.open-in-view=false
위처럼 OSIV = false (default)인 경우 Session의 생존 주기는 아래와 같습니다.
//application.properties
spring.jpa.open-in-view=true
OSIV = true로 설정하는 경우 Session의 생존 주기는 아래와 같이 변경됩니다.
성능이나, 보안측면에서 이슈가 발생할 수 있기 때문에 개인적으로는 권장하지 않는 방법입니다😅
어쩌면 가장 이론적인 해결방법입니다.
Lazy 전략으로 인해 예외가 발생하니 Eager 전략을 사용하자는,,
하지만 Eager 전략은 모두가 알다싶이 성능면에서 추천하지 않는 방법입니다.
LazyInitializationException 이슈는 지연로딩 전략으로 인해 발생하는 문제로 이유와 해결 방법들에 대해서 알아봤습니다.
연관된 프록시 개념이나, 영속성 컨텍스트 개념도 이번 기회에 잘 정리하면 좋을 것 같습니다
모두 해당 이슈에도 당황하지 않고, 슬기로운 개발활동을 응원합니다!
References