즉시로딩과 지연로딩의 이해를 위해서 회원(Member)과 팀(Team)의 다대일 단방향 관계를 예시로 들어보겠습니다.
위 조건에 다대일 단방향 관계를 위한 추가 조건은 아래와 같습니다.
비즈니스 로직 설계 시, 회원 객체와 이와 연관된 팀 객체가 거의 항상 같이 사용된다고 가정해보겠습니다.
그렇다면 회원 객체의 @ManyToOne 어노테이션의 fetch 속성을 FetchType.EAGER 로 지정하여 팀 엔티티의 조회 시점을 회원 엔티티의 조회 시점과 동일 시 하게 할 수 있습니다.
이와 같이 즉시로딩을 사용하려면, 연관관계 어노테이션의 fetch 속성을 FetchType.EAGER 로 지정하면 됩니다.
연관관계 어노테이션
- 엔티티간의 연관관계를 지정해주기 위한 어노테이션입니다.
- @ManyToOne, @OneToMany, @OneToOne, @ManyToMany 가 있습니다.
즉시로딩이 적용된 엔티티 코드는 아래와 같습니다.
Member 클래스 (다대일
에서 다
에 해당합니다.)
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID)
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne // @ManyToOne 의 속성 fetch 의 기본값 은 FetchType.EAGER
@JoinColumn(name = "TEAM_ID")
private Team team;
// Getter, Setter, Constructor...
}
Team 클래스 (다대일
에서 일
에 해당합니다.)
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID)
private Long id;
@Column(name = "NAME")
private String name;
// Getter, Setter, Constructor
}
작성된 엔티티 코드를 통하여 회원 및 팀 객체의 조회 시점을 살펴보도록 하겠습니다.
즉시 로딩 실행 코드
Member member = entityManager.find(Member.class, 0L);
Team team = member.getTeam();
System.out.println("회원 이름 : " + member.getUsername());
System.out.println("팀 이름 : " + team.getName());
위 코드를 실행하였을때, 실행되는 SQL 문은 아래와 같습니다.
SELECT m.MEMBER_ID, m.TEAM_ID, m.USERNAME, t.TEAM_ID, t.NAME
FROM
Member m
LEFT OUTER JOIN
Team t
ON m.TEAM_ID = t.TEAM_ID
WHERE m.MEMBER_ID = 0;
@ManyToOne, @OneToOne 의 fetch 타입의 기본값은 FetchType.EAGER 입니다.
그러므로 즉시로딩을 사용할때 추가로 설정해줄 필요가 없습니다.
@OneToMany, @ManyToMany 의 fetch 타입의 기본값은 FetchType.LAZY 입니다.
즉시로딩을 사용할때 EAGER 타입으로 변경해주어야 합니다.
비즈니스 로직 설계 시, 회원 객체와 이와 연관된 팀 객체가 거의 같이 사용되지 않는다고 가정해보겠습니다.
그렇다면 회원 객체의 @ManyToOne 어노테이션의 fetch 속성을 FetchType.LAZY 로 지정하여 팀 엔티티의 조회 시점을 실제 해당 객체가 사용될때로 늦출 수 있습니다.
이와 같이 지연로딩을 사용하려면, 연관관계 어노테이션의 fetch 속성을 FetchType.LAZY 로 지정하면 됩니다.
즉시로딩이 적용된 엔티티 코드는 아래와 같습니다.
Member 클래스 (다대일
에서 다
에 해당합니다.)
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID)
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// Getter, Setter, Constructor...
}
Team 클래스 (다대일
에서 일
에 해당합니다.)
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID)
private Long id;
@Column(name = "NAME")
private String name;
// Getter, Setter, Constructor
}
작성된 엔티티 코드를 통하여 회원 및 팀 객체의 조회 시점을 살펴보도록 하겠습니다.
지연 로딩 실행 코드
System.out.println("1. 회원 객체 조회");
Member member = entityManager.find(Member.class, 0L);
System.out.println("2. 회원 이름 조회");
System.out.println("회원 이름 : " + member.getUsername());
System.out.println("3. 팀 객체 조회");
Team team = member.getTeam(); // Proxy 객체이기 때문에 실제 조회는 team.getXXX 할때 발생합니다.
System.out.println(team.getClass());
System.out.println("4. 팀 이름 조회");
System.out.println("팀 이름 : " + team.getName()); // Proxy 객체 초기화
System.out.println(team.getClass());
위 코드를 실행하였을 때 출력되는 로그는 아래와 같습니다.
1. 회원객체 조회
SELECT m.MEMBER_ID, m.TEAM_ID, m.USERNAME
FROM
Member m
WHERE m.MEMBER_ID = 0;
2. 회원 이름 조회
회원 이름 : 회원1
3. 팀 객체 조회
class TIL.jpa.Domain.Team$HibernateProxy$hbJa4wRu
4. 팀 이름 조회
SELECT t.TEAM_ID, t.NAME
FROM
Team t
WHERE t.TEAM_ID = 0;
팀 이름 : 팀1
class TIL.jpa.Domain.Team$HibernateProxy$hbJa4wRu
로딩되는 시점에 Lazy 로딩 설정이 되어있는 Team 엔티티는 프록시 객체로 가져옵니다.
후에 실제 객체를 사용하는 시점에(Team을 사용하는 시점에) 초기화가 됩니다.
(즉, DB에 쿼리가 실행됩니다.)
getTeam()으로 Team을 조회하면 프록시 객체가 조회가 됩니다.
(team.getClass() 출력시 Proxy 클래스가 출력됩니다.)
getTeam().getXXX()으로 팀의 필드에 접근 할 때, 쿼리가 실행됩니다.
@OneToMany, @ManyToMany 의 fetch 타입의 기본값은 FetchType.LAZY 입니다.
그러므로 지연로딩을 사용할때 추가로 설정해줄 필요가 없습니다.
@ManyToOne, @OneToOne 의 fetch 타입의 기본값은 FetchType.EAGER 입니다.
즉시로딩을 사용할때 LAZY 타입으로 변경해주어야 합니다.
지연 로딩(LAZY)
즉시 로딩(EAGER)
그 이유는 아래와 같습니다.
1. 즉시 로딩 적용 시, 예상하지 못한 쿼리가 발생할 가능성이 높습니다.
2. 즉시 로딩은 JPQL에서 N+1 문제를 일으킵니다.