Lazy Loading은 실제로 필요한 시점까지 데이터를 "지연"하여 조회하는 방식으로, 데이터베이스와 연결된 Entity 객체들을 필요할 때만 가져와 성능을 최적화하려는 기법입니다.
예를 들어, Comment
가 Todo
와 관계가 있는 상황에서 FetchType.LAZY
를 설정하면 Comment
객체를 조회할 때 Todo
는 즉시 불러오지 않습니다. Todo
가 실제로 필요해지는 시점에 비로소 데이터베이스에서 쿼리를 실행하게 됩니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "todo_id")
private Todo todo;
이 설정을 통해 Comment
를 조회하더라도 Todo
는 필요해질 때까지 조회하지 않게 되므로, 불필요한 데이터베이스 호출을 줄일 수 있습니다.
Proxy 객체가 Lazy Loading의 핵심입니다. Comment
를 조회할 때 Todo
는 즉시 사용되지 않기 때문에, Todo
자리에는 대신 Proxy 객체가 들어갑니다.
Proxy 객체는 진짜 데이터가 필요해지는 순간 데이터베이스에서 데이터를 조회하여 가져옵니다.
예를 들어, Comment
를 조회한 후 Todo
의 title
을 참조하게 되면 그때서야 Todo
의 데이터가 데이터베이스에서 조회됩니다.
LazyInitializationException은 Lazy Loading을 사용할 때 세션(Session)이 없을 때 발생하는 오류입니다.
public CommentResponse retrieveCommentById(Long commentId) {
Comment comment = commentService.retrieve(commentId);
return CommentResponse.from(comment); // 여기서 Lazy Loading 발생!
}
위 코드에서 commentService.retrieve()
가 트랜잭션 범위를 벗어나면, 영속성 컨텍스트도 사라져서 Comment
와 연결된 Todo
의 데이터에 접근할 때 LazyInitializationException이 발생할 수 있습니다.
Lazy Loading
이 필요한 시점이 트랜잭션 내부로 유지되도록 코드를 수정하는 것입니다.CommentService
에서 필요한 데이터를 미리 조회하여 반환하도록 리팩토링하는 방식입니다.LazyInitializationException
이 발생하지 않습니다.spring:
jpa:
open-in-view: false # OSIV 비활성화 설정
결론: Lazy Loading, LazyInitializationException, OSIV까지 살펴본 결과, 무작정 옵션을 켜두기보다는 필요한 시점에 트랜잭션 내에서 적절하게 데이터를 조회하도록 하는 것이 중요하다는 점을 확인할 수 있었습니다. 📝