지연로딩과 즉시로딩
JPA는 객체 그래프로 연관된 객체들을 탐색하는데, 객체가 DB에 저장되어있으므로 마음껏 탐색하기 어렵다. 따라서 처음부터 객체를 조회하지 않고, 실제 사용하는 시점에 조회한다.(이때 프록시를 이용한다.) 이것을 지연로딩이라고 한다.
하지만, 자주 함께 사용하는 객체들은 조인을 사용해서 함께 조회하는게 효과적이다. 이렇게 로딩하는 방식을 즉시 로딩이라고 한다.
아래 예제를 보자.
@Data
@Entity
public class Member {
private String username;
@ManyToOne
private Team team;
}
@Data
@Entity
public class Team {
private String name;
}
//비즈니스로직 1 : 회원의 팀을 조회
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("팀 : " + team.getName());
//비즈니스로직 2 : 회원의 이름을 조회
Member member = em.find(Member.class, memberId);
System.out.println("이름 : " + member.getUserName());
위의 상황이 지연로딩이 필요한 곳이다.
비즈니스로직1에서는 Team까지 조인해서 조회해야하지만, 비즈니스 로직 2에서는 Team을 조인하는 일이 불필요하고 비효율적이다.
그럼 지연로딩을 할 때 쓴다는 프록시가 뭘까? Hibernate기준으로 설명하겠다.
* 프록시를 구현하는 것은 JPA명세서에 없고, JPA구현체로 위임하게 되어있다. 따라서 Hibernate등의 구현체에 따라 다를 수 있다.
프록시
프록시는 실제 엔티티객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체이다.
예를 들어 em.getReference()를 하면 proxy객체를 반환한다.
proxy객체는 실제 객체와 겉 모양이 똑같고, 실제 객체를 참조하고 있다. proxy를 초기화 한 후 (DB를 조회해서 실제 엔티티 객체를 생성) 에는 proxy객체의 메소드를 사용하면 proxy가 참조하는 실제 객체의 메소드가 실행된다.
proxy객체를 초기화하는 과정에서 영속성컨텍스트에 이미 있으면, 실제 객체를 반환하고 없으면 데이터베이스를 조회한다. 따라서 proxy는 영속성컨텍스트의 도움을 받을 수 있어야 하므로 준영속상태이면 (Hibernate기준) LazyInitializationException예외가 발생한다.
지연로딩
지연로딩은 아래처럼 사용한다.
@Data
@Entity
public class Member {
private String username;
@ManyToOne(fetch = FethType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
em.find(Member.class, "member1") // 프록시 객체 초기화
지연로딩을 사용하면, 먼저 프록시 객체가 할당이 되었다가, 실제 데이터가 필요한 순간에 데이터를 조회하여 프록시 객체를 초기화한다.
데이터를 조회할 때 영속성 컨텍스트에 있으면 프록시 객체를 사용할 이유가 없으므로, 실제 객체를사용한다.