[JPA] 즉시 로딩과 지연 로딩

3Beom's 개발 블로그·2022년 12월 1일
0

SpringJPA

목록 보기
10/21
post-custom-banner

출처

본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 수강하며 기록한 필기 내용을 정리한 글입니다.

-> 인프런
-> 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의


0. 예시 설정

  • 엔티티 두 개를 설정한다.
    • Member
    • Team
  • 두 엔티티는 다음과 같이 다대일 연관관계를 갖는다.
    • Member : Team = N : 1
  • 각 엔티티는 다음과 같이 구현되어 있다.

(GETTER, SETTER 생략)

< Member >

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "MEMBER_NAME")
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

< Team >

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    @Column(name = "TEAM_NAME")
    private String name;
}

1. 즉시 로딩과 지연 로딩

  • 이전 프록시 글에서 다루었듯, Member 객체를 조회할 때 다음 두 가지 방식이 있다.
    • Member 객체를 조회할 때마다 Team 객체도 같이 조회한다. : 즉시 로딩
    • Member 객체를 조회할 때는 Member 데이터만 조회하고, Team 객체가 활용될 때 Team 데이터를 조회한다. : 지연 로딩
  • 본 글에 앞서 프록시에 대해 알아야 한다.
    -> [JPA] 프록시(Proxy) 기본

2. 지연 로딩

  • 지연 로딩은 Member 엔티티의 Team 객체 필드를 프록시 객체로 설정하여 Team 객체가 활용될 때 TEAM 테이블을 조회하는 방식이다.
  • @ManyToOne 어노테이션에 fetch 속성을 설정하여 구현할 수 있다.
    -> @ManyToOne(fetch = FetchType.LAZY)

< Member >

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "MEMBER_NAME")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
  • 이렇게 지연 로딩으로 설정해 두면 Member 엔티티만 조회했을 때 TEAM 테이블과의 join 없이 MEMBER 테이블만 조회하게 된다.
Team team1 = new Team();
team1.setName("team1");
entityManager.persist(team1);

Member member1 = new Member();
member1.setName("member1");
member1.setTeam(team1);
entityManager.persist(member1);

entityManager.flush();
entityManager.clear();

Member findMember1 = entityManager.find(Member.class, member1.getId());

  • 여기서 Member 객체 내 Team 객체 필드의 타입 정보를 출력해 보면 프록시 객체로 설정되어 있는 것을 확인할 수 있다.
Member findMember1 = entityManager.find(Member.class, member1.getId());
System.out.println("team Class : " + findMember1.getTeam().getClass());

  • 이후 Team 객체 필드의 name 값을 활용해 보면, 프록시 초기화가 일어나는 것을 확인할 수 있다.
Member findMember1 = entityManager.find(Member.class, member1.getId());
System.out.println("team Name : " + findMember1.getTeam().getName());

  • fetch 속성을 FetchType.LAZY로 설정하면, 해당 객체 필드를 프록시 객체로 설정하여 지연 로딩이 가능해 진다.

3. 즉시 로딩

  • 즉시 로딩은 Member 엔티티를 조회할 때 Team 객체 필드도 바로 조회하는 방식이다.
    • MEMBER 테이블과 TEAM 테이블을 join 하여 가져오거나, 각각 쿼리를 보내는 방식으로 동작한다.
  • Team 객체 필드를 프록시 객체가 아닌 실제 엔티티로 설정한다.
  • 즉시 로딩도 fetch 속성으로 다음과 같이 구현될 수 있다.
    • @ManyToOne(fetch = FetchType.EAGER)

< Member >

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "MEMBER_NAME")
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

4. 지연 로딩만 활용하자

  • ⭐️가급적이면 지연 로딩만 사용하는 것이 좋다.⭐️
  • 그 이유는 다음과 같다.

4-1. 지연 로딩만 활용해야 하는 이유

(1) 즉시 로딩을 활용하면 DB와의 소통이 무거워진다.

  • 예시에서는 단순히 Member와 Team이지만 만약 수십개의 테이블이 서로 복잡하게 연관관계로 매핑되어 있을 경우, 즉시 로딩으로 동작하면 매번 조회할 때마다 join 쿼리가 나간다. (혹은 따로 나가거나)
  • 이는 DB 성능 저하를 야기할 수 있다.

(2) 즉시 로딩은 JPQL에서 N + 1 문제를 일으킨다.

  • 만약 JPQL을 활용하여 다음과 같이 조회하게 될 경우에 대해 생각해 보자.
List<Member> members = entityManager.createQuery("select m from Member m", Member.class).getResultList();
  • 이는 곧 다음과 같이 SQL 문으로 번역되어 MEMBER 테이블로 전달된다.
SELECT * FROM MEMBER
  • 여기서 TEAM 테이블에는 쿼리가 전달되지 않는다.
    • JPQL은 DB로 바로 쿼리를 전달하고, 위 SQL문에서는 MEMBER 테이블만 조회하고 있기 때문이다.
  • 이 때 만약 즉시 로딩으로 설정되어 있을 경우, 이를 구현하기 위해 JPA가 TEAM 테이블에 쿼리를 보내게 되는데, 모든 Member 데이터 각각에 대해 하나씩 다 보낸다.
    • MEMBER 테이블에서 조회한 데이터 개수만큼 TEAM 테이블에 쿼리가 전달된다.
  • 즉, 다음과 같이 N + 1 개의 쿼리가 DB로 전달된다.
    • 처음 MEMBER 테이블 조회하는 쿼리 1개
    • MEMBER 테이블 조회 결과 N개만큼 TEAM 테이블 조회하는 쿼리 N개

(3) 지연 로딩으로 적용해도 즉시 로딩을 구현할 수 있는 방법이 있다.

  • JPQL로 구현할 수 있다. : join fetch
    List<Member> members = entityManager.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();
    • 대부분 이 방법을 많이 활용한다.
  • @EntityGraph 어노테이션을 활용한다.

5. 연관관계 매핑 별 기본값

  • ~~One 으로 끝나는 연관관계 매핑은 즉시 로딩이 Default이다.
    • @ManyToOne
    • @OneToOne
      => 지연 로딩으로 설정해야 한다.
  • ~~Many로 끝나는 연관관계 매핑은 지연 로딩이 Default이다.
    • @OneToMany
    • @ManyToMany
profile
경험과 기록으로 성장하기
post-custom-banner

0개의 댓글