즉시 로딩과 지연 로딩을 설명하기 전에 프록시 객체에 대해서 알아야 한다. 프록시 객체는 간단하게 말하면 '데이터베이스 조회를 지연하는 가짜 객체'라고 말할 수 있다.
JPA에서 식별자로 엔티티 하나를 조회할 때 EntityManager.find()를 사용한다. 이 때 영속성 컨텍스트에 엔티티가 없으면 데이터베이스를 조회한다. 이런식으로 엔티티를 직접 조회하면 조회한 엔티티를 사용하든 하지 않든 데이터베이스를 조회하게 된다. 만약 엔티티를 실제 사용하는 시점(여기서 말하는 실제 사용하는 시점은 member.getName() 같이 엔티티를 실제 사용하는 상황을 말함 - 프록시 초기화)까지 미루고 싶다면 EntityManager.getReference()를 사용하면 된다. 바로 이 때 데이터베이스 접근을 위임한 프록시 객체가 생성되어 반환된다. 프록시 객체를 생성할 때 JPA는 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다.
실제 엔티티 클래스를 상속 받아서 만들어지므로 겉 모양이 같아서 진짜 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있다.
프록시 객체는 실제 엔티티 객체에 대한 참조를 보관하다가 프록시 객체의 메소드를 호출하면 실제 엔티티 객체의 메소드를 호출한다.
프록시 객체는 member.getName() 같이 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 초기화라고 한다. 다음은 프록시 초기화를 하는 과정이다.
1. 프록시 객체에 member.getName()을 호출해서 실제 데이터 조회한다.
2. 실제 엔티티 객체가 없으면 영속성 컨텍스트에 엔티티 생성을 요청하는데 이것을 초기화라 한다.
3. 영속성 컨텍스트는 데이터베이스를 조회하여 실제 엔티티 객체를 생성한다.
4. 프록시 객체는 위에서 생성된 실제 엔티티 객체의 참조를 보관한다.
5. 프록시 객체가 실제 엔티티 객체의 getName()을 호출하여 결과를 반환한다.
즉시 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.EAGER로 설정한다.
@Entity
public class Member() {
//...
@ManyToOne(fetch = FetchType.EAGER)
private Team team;
//...
}
예를 들면 아래와 같은 연관관계를 가진 엔티티가 있다고 생각하자.
Member member = em.find(Member.class, "member1"); Team team = member.getTeam();
만약 em.find()로 Member를 조회하면 연관관계에 있는 Team도 같이 조회된다. JPA 구현체는 이 처럼 연관관계에 있는 테이블을 조회할 때 최적화를 위해 가능하면 조인 쿼리를 사용하여 쿼리를 1개만 만든다.
즉시 로딩을 하게 되면 연관관계에 있는 엔티티 객체도 조회한다.
지연 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.LAZY로 설정한다.
@Entity
public class Member() {
//...
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
//...
}
예를 들면 아래와 같은 연관관계를 가진 엔티티가 있다고 생각하자.
Member member = em.find(Member.class, "member1"); Team team = member.getTeam(); // 프록시 객체 System.out.println(team.getName()); // 팀 객체 실제 사용시점
em.find()를 호출하면 Member 객체만 조회하고 Team 객체는 조회하지 않는다. 대신 조회한 Member 객체의 team 멤버변수에 프록시 객체를 넣어 둔다.
이 프록시 객체는 실제 사용될 때까지 데이터 로딩을 미룬다. 실제 데이터가 필요한 순사니 되어서야 데이터베이스를 조회해서 프록시 초기화를 한다.
처음부터 연관된 엔티티를 모두 영속성 컨텍스트에 올려두는 것은 현실적이지 않고, 필요할 때마다 SQL을 실행해서 연관된 엔티티를 지연 로딩하는 것도 최적화 관점에서 보면 꼭 좋은 것만은 아니다. 그래서 이 둘을 적재적소에 맞게 효율적으로 사용해야 한다.
예를 들어 연관관계에 있는 엔티티를 바로 실제 사용하는 상황이 많으면 즉시 로딩으로 설정해서 조인 쿼리 하나로 모두 조회하는 것이 효율적일 것이고 반대되는 상황에는 실제 필요한 엔티티만 조회해서 쓸데없는 쿼리 생성을 하지 않는것이 효율적일 것이다.