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

이유석·2023년 1월 23일
1

JPA - Entity

목록 보기
11/14
post-thumbnail
post-custom-banner

즉시로딩과 지연로딩의 이해를 위해서 회원(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
소통을 중요하게 여기며, 정보의 공유를 통해 완전한 학습을 이루어 냅니다.
post-custom-banner

0개의 댓글