[JPA] 즉시로딩(EAGER)과 지연로딩(LAZY) (왜 LAZY 로딩을 써야할까?) (1)

Done is better than perfect·2022년 3월 21일
4

JPA

목록 보기
12/12
post-thumbnail

이전 글에서 Proxy에 대해 살펴보았다.

Proxy는 이 글의 주제인 즉시로딩과 지연로딩을 구현하는데 중요한 개념인데, 일단 원리는 미뤄두고 즉시로딩과 지연로딩이 무엇인지에 대해 먼저 알아보자.


위와 같은 상황을 가정하고 엔티티를 작성해보자!

@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;
}
@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는 위와 같이 아~주 간단하다. Team과 Member는 양방향 연관관계이고, 연관관계의 주인은 Member이다.(왜 연관관계의 주인이 Member인지 이해되지 않는다면.. 연관관계 글부터 다시 읽는것을 추천한다😂)

눈 여겨 볼 곳은 'fetch = FetchType.EAGER' 부분이다. 앞서 JPA를 설명하면서 한번도 사용하지 않은 속성인데, FetchType을 EAGER로 설정했다. (참고로 @ManyToOne 매핑의 기본 fetch가 EAGER라서 생략해도 EAGER로 동작한다.)

🎈 예제 코드

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

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

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

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

간단한 예제코드이다. Team 객체와 Member 객체를 각각 만들고 Member 객체의 Setter 메소드를 통해 Team 객체를 셋팅해준 뒤 em.find() 메소드를 통해 Member를 조회한다.

여기서 중요한 점은 분명 Member를 조회했다는 점이다. 이제 결과 로그를 살펴보자

🎉 로그(EAGER일 때)

Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_0_0_,
        member0_.TEAM_ID as TEAM_ID3_0_0_,
        member0_.USERNAME as USERNAME2_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_.MEMBER_ID=?

뭔가 이상하다. 분명 Member를 조회했는데 왜 Team까지 join되어 쿼리가 나간걸까?
이 마법이 fetch = FetchType.EAGER 의 결과이다.
그렇다면 fetch를 LAZY로 바꿔 실행해보자(fetch = FetchType.LAZY)

🎉 로그(LAZY일 때)

Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_0_0_,
        member0_.TEAM_ID as TEAM_ID3_0_0_,
        member0_.USERNAME as USERNAME2_0_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

이번엔 Member만 딱 조회해오는것을 볼 수 있다.
그리고 아래와 같이 Member 객체로 부터 Team 객체의 데이터를 실질적으로 요구하는 코드를 만나면

findMember.getTeam().getName();		// 코드 자체엔 큰 의미가 없음, team 객체의 데이터를 요구하기 위한 코드

🎉 로그(LAZY일 때)

Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_1_0_,
        team0_.name as name2_1_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?

이 때 Team 데이터를 조회하는 쿼리문이 나간다..!

EAGER와 LAZY의 차이가 감이 조금은 오는가?

EAGER는 사전적 의미인 열심인, 열렬한 처럼 Member를 조회하면 연관관계에 있는 Team 역시 함께 조회는 반면에, LAZY는 게을러서 Member만 조회해오고 연관관계에 있는 나머지 데이터는 조회를 미룬다.

언뜻보면 EAGER가 더 좋아보이기도 한다.
그렇다면 어떤 상황에 EAGER를 또는 LAZY 로딩을 사용해야 할까?
먼저 지극히 이론적으로만 설명하자면 다음과 같다.

비지니스 로직 상 Member 데이터가 필요한 곳에 대부분 Team의 데이터 역시 같이 사용 할 필요가 있다면 어떨까? FetchType을 EAGER로 설정하여 항상 Member와 Team을 같이 조회해오는 것이 더 좋을 것이다.

Member를 사용하는 곳 대부분에서 Team 데이터가 필요하지 않다면? FetchType을 LAZY로 설정하여 Member만 조회하고, 드물게 Team이 필요할 땐 그 때 Team에 대한 쿼리를 한번 더 날려 조회하는것이 좋을 것이다.


🔑
하지만 위에서 언급했다시피 이 부분은 지극히 이론적인 설명이며, 실무에서는 EAGER LOADING을 사용하지 않는 것을 권장한다. 다음 글에서 본격적으로 왜 LAZY LOADING을 사용해야하는지, EAGER LOADING은 어떤 문제점을 발생시키는지 알아보겠다!

0개의 댓글