jpa 지연로딩lazy, 즉시로딩eager, 프록시

즉시로딩과 지연로딩

  • JPA나 그것의 구현체는 기본적으로 즉시로딩을 이상적으로 본다. 그러니까 해당 엔티티의 연관관계를 가진 객체가 채워진 형태를 이상적으로 본다.
  • 하지만 이 경우 n+1의 문제나 불필요한 데이터의 호출 등을 만들기 때문에, 실무에서는 무조건 지연로딩을 사용한다.
  • 즉시로딩의 장점인 데이터의 일괄 호출로 인한 효율은 fetch 나 기타 기법으로 해소 가능하다.
  • 지연로딩은 프록시 기법을 사용하기 때문에 앞서 정리한 jpa 와 프록시와 관련한 블로그를 참고 바란다.

즉시로딩

@Entity
@Setter
@Getter
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

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

@Entity
@Setter
@Getter
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
}
Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setName("kimA");
member.setTeam(team);
em.persist(member);

em.flush();
em.clear();

final Member findMember = em.find(Member.class, member.getId());

final Team getTeamByMember = findMember.getTeam();
System.out.println("team class : "+getTeamByMember.getClass());
getTeamByMember.getName();

tx.commit();
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.name as name2_0_0_,
        member0_.TEAM_ID as team_id3_0_0_,
        team1_.TEAM_ID as team_id1_1_1_,
        team1_.name as name2_1_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.TEAM_ID 
    where
        member0_.id=?
team class : class jpa6_proxy.b_lazy.Team
  • 즉시로딩으로 호출할 경우 join 을 통해 데이터를 출력한다.
  • 그리고 Team의 클래스가 Team임을 확인할 수 있다.

즉시로딩의 n+1 문제

  • 위와 같이 간단한 경우 특별한 문제를 만들지 않는다.
  • 하지만 지연 로딩은 n+1이란 치명적인 문제를 가지고 있다. n+1이란, 의도한 1개의 쿼리와 함께 의도하지 않은 n개의 쿼리가 발생한다는 의미이다.
Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setName("kimA");
member.setTeam(team);
em.persist(member);

Team team2 = new Team();
team2.setName("teamA");
em.persist(team2);

Member member2 = new Member();
member2.setName("kimA");
member2.setTeam(team2);
em.persist(member2);

em.flush();
em.clear();

em.createQuery("select m from Member m").getResultList();

tx.commit();
Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.id as id1_0_,
            member0_.name as name2_0_,
            member0_.TEAM_ID as team_id3_0_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.TEAM_ID as team_id1_1_0_,
        team0_.name as name2_1_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
Hibernate: 
    select
        team0_.TEAM_ID as team_id1_1_0_,
        team0_.name as name2_1_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
  • 위의 JPQL의 경우 member 테이블의 모든 데이터를 출력하는 쿼리이다. 이 경우 우리의 기대는 select * from member 라는 하나의 쿼리이다.
  • 하지만 의도와 달리 두 번의 select 쿼리를 한 것을 확인할 수 있다. JPA는 먼저 요청한 쿼리 한 번 select * from member 을 수행한다. 그리고 Team team 객체를 채워야 함을 확인하고, 그것의 객체의 수만큼 select * from team where team_id = ? 을 쿼리한다.

지연로딩

  • 지연로딩을 세팅하는 방법은 아래와 같이 간단하게 수행한다.
@Entity
@Setter
@Getter
public class Member {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setName("kimA");
member.setTeam(team);
em.persist(member);

em.flush();
em.clear();

final Member findMember = em.find(Member.class, member.getId());

final Team getTeamByMember = findMember.getTeam();
System.out.println("getTeamByMember.getClass() = " + getTeamByMember.getClass());
System.out.println("=== before ===");
getTeamByMember.getName();
System.out.println("=== after ===");
getTeamByMember.getClass() = class jpa6_proxy.c_lazy.Team$HibernateProxy$JBUnp834
=== before ===
Hibernate: 
    select
        team0_.TEAM_ID as team_id1_1_0_,
        team0_.name as name2_1_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
=== after ===
  • 지연로딩으로 할 경우 프록시로 리턴함을 확인할 수 있다.
  • 프록시를 getName()으로 강제초기화 할 때 실제 엔티티 객체를 호출하고 데이터를 출력함을 확인할 수 있다.

정리

  • JPA는 이론적으로 지연로딩과 즉시로딩을 해당 객체의 조건에 따라 적절하게 쓰는 것을 의도했다. Member 를 출력할 때 Team 을 사용하는 경우가 80퍼센트 이상이라면, 즉시로딩을 사용하는 것이 좋을테니까. 하지만 실제 하이버네이트는 즉시로딩으로 인한 문제가 매우 많다. 그러므로 즉시로딩은 사용하면 안된다.
  • 무조건 지연로딩을 사용한다.
  • 지연로딩의 부족한 부분은 fetch 조인이나 엔티티 그래프 등을 사용한다.