즉시 로딩, 지연 로딩

다음과 같이 N : 1로 연관관계 매핑된 2개의 엔티티가 있다고 할 때,

 

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

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

서로 연관관계 매핑되는 칼럼에 다음과 같이

fetch = FetchType.LAZY/EAGER를 통해서 지연로딩/즉시로딩으로 설정할 수 있다.

 


지연 로딩


// Team 엔티티를 생성한다
Team team = new Team();
team.setName("teamA");
em.persist(team);

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

em.flush();
em.clear();
            
Member m = em.find(Member.class, member1.getId());

System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
            
System.out.println("===========================");
m.getTeam().getName();
System.out.println("===========================");
tx.commit();
/* 결과

insert 
        into
            Team
            (name, TEAM_ID) 
        values
            (?, ?)
            
insert 
        into
            Member
            (TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?)

// Member를 조회할 경우 member 조회에 대한 Query만 발생한다.
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.TEAM_ID as TEAM_ID3_3_0_,
        member0_.USERNAME as USERNAME2_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

// 다음과 같이 Team이 실제로 사용되지 않는 시점에서는 프록시 객체로 생성 된 것을 확인할 수 있다.
m.getTeam().getClass() = class hellojpa.Team$HibernateProxy$lBemz2Kd

// 다음과 같이 실제 Team 객체를 사용하는 시점에 조회 Query가 발생하는 것을 볼 수 있다.
===========================
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.name as name2_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
===========================
*/

 


즉시 로딩


@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

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

다음과 같이 즉시 로딩 전략으로 변경 후 결과를 확인해보면,

 

// 조회 시 join을 통해서 필요한 엔티티도 초기에 조회하여 채워진다.
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.TEAM_ID as TEAM_ID3_3_0_,
        member0_.USERNAME as USERNAME2_3_0_,
        team1_.TEAM_ID as TEAM_ID1_5_1_,
        team1_.name as name2_5_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.TEAM_ID 
    where
        member0_.MEMBER_ID=?

// 프록시 객체가 아닌 실제 엔티티인 것을 확인할 수 있다.
m.getTeam().getClass() = class hellojpa.Team

===========================
===========================

프록시와 즉시로딩 주의


1.실무에서는 가능하면 즉시로딩만 사용하자.

-> 즉시 로딩을 적용 시 예상치 못한 SQL이 발생한다.

->즉시 로딩은 JPQL 사용 시 N+1 문제를 일으킨다.

 

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

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

Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);

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

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

// JPQL 사용
List<Member> members = em.createQuery("select m from Member m", Member.class)
                    .getResultList();
Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.MEMBER_ID as MEMBER_I1_3_,
            member0_.TEAM_ID as TEAM_ID3_3_,
            member0_.USERNAME as USERNAME2_3_ 
        from
            Member member0_
            
// 다음과 같이 각각의 멤버에 대해서 Team을 조회하기 위한 별도의 쿼리가 발생한다.
// N + 1 : 하나의 쿼리를 수행할 때 부가적으로 N개만큼 쿼리가 발생하는 현상
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.name as name2_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_5_0_,
        team0_.name as name2_5_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?

2.ManyToOne, OneToOne은 기본이 즉시 로딩이다.

-> LAZY로 설정해주자


지연 로딩 활용



참조 자료


자바 ORM 표준 JPA 프로그래밍 - 기본편 (인프런 김영한님 강의)

'DB > JPA' 카테고리의 다른 글

영속성 컨텍스트  (0) 2023.05.10
Proxy  (0) 2023.03.22