JPA - 즉시로딩과 지연로딩 (FetchType.EAGER or LAZY)

이유석·2023년 1월 23일
1

JPA - Entity

목록 보기
11/14
post-thumbnail

즉시로딩과 지연로딩의 이해를 위해서 회원(Member)과 팀(Team)의 다대일 단방향 관계를 예시로 들어보겠습니다.

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 다수의 회원은 하나의 팀에 소속될 수 있습니다.
  • 즉, 회원관 팀은 다대일(N:1)의 관계입니다.

위 조건에 다대일 단방향 관계를 위한 추가 조건은 아래와 같습니다.

  • 회원 객체와 팀 객체는 단방향 관계입니다.
  • 회원 객체(Member)는 Member.team 필드를 통해서 회원이 속한 팀 객체(Team)에 접근할 수 있습니다.
  • 팀 객체(Team)는 팀에 속한 회원 객체(Member)에 접근할 수 없습니다.

즉시로딩 (FetchType.EAGER)

비즈니스 로직 설계 시, 회원 객체와 이와 연관된 팀 객체가 거의 항상 같이 사용된다고 가정해보겠습니다.

그렇다면 회원 객체의 @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;
  • 회원과 팀을 즉시 로딩으로 설정하였기 때문에, 동시에 조회됩니다.
  • 대부분의 JPA 구현체는 즉시로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용합니다.

연관관계 어노테이션

  • @ManyToOne, @OneToOne 의 fetch 타입의 기본값은 FetchType.EAGER 입니다.
    그러므로 즉시로딩을 사용할때 추가로 설정해줄 필요가 없습니다.

  • @OneToMany, @ManyToMany 의 fetch 타입의 기본값은 FetchType.LAZY 입니다.
    즉시로딩을 사용할때 EAGER 타입으로 변경해주어야 합니다.

소스코드

지연로딩 (FetchType.LAZY)

비즈니스 로직 설계 시, 회원 객체와 이와 연관된 팀 객체가 거의 같이 사용되지 않는다고 가정해보겠습니다.

그렇다면 회원 객체의 @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)

  • 연관된 엔티티를 즉시 조회합니다.
  • 하이버네이트는 가능하면 SQL 조인을 사용해서 한 번에 조회합니다.

fetch 타입 설정 시 주의사항

결론은 지연 로딩(FetchType.LAZY) 를 사용하는 것 입니다.

그 이유는 아래와 같습니다.

1. 즉시 로딩 적용 시, 예상하지 못한 쿼리가 발생할 가능성이 높습니다.

2. 즉시 로딩은 JPQL에서 N+1 문제를 일으킵니다.

  • 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(N) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 됩니다.
profile
소통을 중요하게 여기며, 정보의 공유를 통해 완전한 학습을 이루어 냅니다.

0개의 댓글

관련 채용 정보