[JPA] 지연 로딩 (Lazy Loading)
프록시에 대해 설명한 글에서 간단한게 JPA가 제공하는 지연 로딩이라는 기능에 대해 설명했었다. JPA는 엔티티를 조회하는 과정에서 연관엔티티의 생성을 뒤로 미룰 수 있는 지연 로딩(Lazy Loading)이라는 기능을 제공한다. 이번 포스팅에서는 지연 로딩이 왜 필요하고, 어떻게 사용하는지에 대해 알아보자.
프록시 패턴 (Proxy Pattern)
스프링이나 JPA를 공부하다보니 프록시(Proxy)라는 개념이 자주 등장한다. 프록시란 진짜 객체를 모방한 가짜객체 정도로 알고 있었는데, 근본적으로 왜 프록시를 사용하고, 프록시가 어떤 부분에
ohreallystore.tistory.com
자바 ORM 표준 JPA 프로그래밍 - 교보문고
스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임 | ★ 이 책에서 다루는 내용 ★■ JPA 기초 이론과 핵심 원리■ JPA로 도메인 모델을 설계하는 과정을 예제 중심으로
www.kyobobook.co.kr
참고도서) 자바 ORM 표준, JPA 프로그래밍
즉시로딩과 지연로딩
즉시 로딩 (eager loading)
어떤 엔티티를 조회하면 연관된 엔티티까지 함께 조회하게 된다. 예를 들어 회원과 팀 엔티티가 다대일 연관관계라고 하자. 그러면 회원 엔티티 조회시, 팀 엔티티까지 함께 조회되게 된다.
Member member = em.find(Mmember.class, 1L);
위와 같인 find 메서드를 통해 조회를 하면 회원 엔티티뿐만 아니라 팀엔티티가 동시에 조회된다. 이러한 방식을 즉시로딩(eager loading)이라고 한다.
Team team = member.getTeam();
그래서 이후 team 엔티티를 사용하려 하면 객체 그래프 탐색을 통해 team 엔티티를 가져온다. (팀 엔티티는 이미 생성되어 있음)
즉시로딩을 통해 조회를 하면 연관된 엔티티가 모두 함께 조회가 된다고 했다. 그렇다면 쿼리도 여러개가 실행될까? 답은 아니다. JPA는 즉시 로딩을 최적화하기위해 조인을 사용하여 한개의 쿼리만으로 모든 엔티티를 조회한다.
아래 쿼리를 보면 조인을 통해 팀 테이블과 회원테이블을 하나의 쿼리로 조회하는 것을 볼 수 있다.
SELECT m.*, t.*
FROM Member m OUTER JOIN Team t ON m.team_id = t.id
WHERE m.member_id = 1
즉시 로딩은 JPA의 디폴트로 설정되어있어 따로 애노테이션을 붙여줄 필요가 없다. 만약 붙여주고 싶다면 연관관계 애노테이션에 fetch 설정을 FetchType.EAGER로 해주면 된다.
@Entity
pulic class Member {
...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
지연 로딩 (lazy loading)
지연 로딩(lazy loading)은 즉시 로딩과 달리 엔티티 조회시, 해당 엔티티만 생성하고, 나머지 엔티티는 사용시점에 생성한다.
Member member = em.find(Mmember.class, 1L);
지연 로딩을 통해 회원 엔티티를 조회하면 회원 엔티티는 생성되지만 회원 엔티티와 연관된 팀 엔티티는 생성되지 않는다. 대신 팀 엔티티의 프록시(proxy)가 생성되게 된다. 프록시는 팀 엔티티를 상속한 가짜 엔티티로 사용자는 이 엔티티가 진짜인지 가짜인지 구별할 수 없다. 대신 프록시를 통해 메서드를 호출하면 프록시는 그제서야 진짜 엔티티를 생성하여 메서드를 호출한다.
Team team = member.getTeam(); // team은 프록시 객체이다
String teamname = team.getName(); // 이때 팀 엔티티가 생성된다
getTeam() 메서드를 호출하면 객체 그래프 탐색을 통해 프록시 객체를 반환한다. 그후 팀 엔티티의 메서드를 호출하면 프록시는 팀 엔티티를 생성하고 getName() 메서드를 실행한다. 즉, 엔티티가 사용 시점에 생성되는 것이다.
지연 로딩을 엔티티에 적용하기 위해서는 연관관계 애노테이션의 fetch 속성을 FetchType.LAZY로 설정해주면 된다.
@Entity
pulic class Member {
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
모든 연관관계에 지연로딩을 적용하자.
즉시 로딩을 통해 엔티티를 조회하면 너무 많은 엔티티가 동시에 조회되게 된다. 이는 JPA의 성능을 저하시킬 수 있다. 또한 사용하지도 않을 엔티티들을 조회할 가능성이 높다. 이는 매우 비효율적이다. 그렇기 때문에 일단은 현재 조회한 엔티티만을 생성하고 연관된 다른 엔티티들을 실제 사용할 시점까지 생성을 미루는 것이다. 또한 이렇게 되면 아예 사용하지 않는 엔티티는 생성이 되지 않는다. 그러므로 대부분의 경우 지연로딩을 사용하는 것이 좋다.
물론, 두 엔티티가 거의 함께 사용된다면 즉시 로딩을 통해 두 엔티티중 하나만 조회되도, 둘다 함께 생성되도록 하는것이 더 효율적일 수 있다. 하지만 실제 개발과정에서 엔티티의 사용 관계를 예측하기가 어렵고, 서비스가 확장 됨에 따라 달라지기 십상이다. 예를들어 개발 초기에는 회원과 팀이 거의 함께 쓰여서 즉시로딩을 설정해두었지만, 서비스가 확장되면서 회원과 팀이 따로 사용되는 경우가 많아지게 되면 되려 성능이 저하 될 수 있다. 그러므로 가장 추천하는 전략은 모든 연관관계에 지연 로딩을 적용하는 것이다.