반응형

지연 로딩(LAZY)

내부 메커니즘은 위의 그림과 같다.
로딩되는 시점에 Lazy 로딩 설정이 되어있는 Team 엔티티는 프록시 객체로 가져온다.
후에 실제 객체를 사용하는 시점에(Team을 사용하는 시점에) 초기화가 된다. DB에 쿼리가 나간다.
- getTeam()으로 Team을 조회하면 프록시 객체가 조회가 된다.
- getTeam().getXXX()으로 팀의 필드에 접근할 때 쿼리가 나간다.

대부분 비즈니스 로직에서 Member와 Team을 같이 사용한다면?

  • 이런 경우 LAZY 로딩을 사용한다면 SELECT 쿼리가 따로따로 2번 나간다.
  • 네트워크를 2번 타서 조회가 이루어 진다는 이야기이므로 손해다.
  • 이때는 JPQL의 fetch join을 통해서 해당 시점에 한방 쿼리로 가져와서 쓰도록 한다.

즉시 로딩(EAGER)

fetch 타입을 EAGER로 설정하면 된다. (@ManyToOne, @OneToOne... @XXXToOne은 기본이 EAGER)
대부분의 JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회하려고 한다.
이렇게 하면 실제 조회할 때 한방 쿼리로 다 조회해온다.
실행 결과를 보면 Team 객체도 프록시 객체가 아니라 실제 객체이다.

프록시와 즉시 로딩 주의할 점

  • 실무에서는 가급적 지연 로딩만 사용한다.
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
    • @ManyToOne이 5개 있는데 전부 EAGER로 설정되어 있다고 해보자.
      • 조인이 5개 일어난다. 실무에선 테이블이 더 많다
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
    • 실무에서 복잡한 쿼리를 많이 풀어내기 위해서 JPQL을 많이 사용한다.
    • EAGER는 반환하는 시점에 다 조회가 되어 있어야 한다. 따라서 Member를 다 가져오고 나서 그 Member와 연관된 Team을 다시 다 가져온다.
    • ex) 멤버가 2명이고 팀도 2 개다. 각각 다른 팀이다.
Team team1 = new Team();
team1.setName("teamA");
em.persist(team1);
​
Team team2 = new Team();
team2.setName("teamB");
em.persist(team2);
​
Member member1 = new Member();
member1.setUsername("memberA");
em.persist(member1);
member1.changeTeam(team1);
​
Member member2 = new Member();
member2.setUsername("memberB");
em.persist(member2);
member2.changeTeam(team2);
​
em.flush();
em.clear();
​
List<Member> members = em
                .createQuery("select m from Member m", Member.class)
  .getResultList();
​
tx.commit();

실행 결과를 보면

  1. 멤버를 조회해서 가져온다
  2. Member들의 Team이 채워주기 위해 TEAM을 각각 쿼리 날려서 가져온다.

여기서 N+1의 문제가 나온다.
-> 쿼리 1개를 날렸는데 그것 때문에 추가 쿼리가 N개 나간다는 의미이다.

Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.id as id1_4_,
            member0_.createdBy as createdB2_4_,
            member0_.createdDate as createdD3_4_,
            member0_.lastModifiedBy as lastModi4_4_,
            member0_.lastModifiedDate as lastModi5_4_,
            member0_.age as age6_4_,
            member0_.description as descript7_4_,
            member0_.locker_id as locker_10_4_,
            member0_.roleType as roleType8_4_,
            member0_.team_id as team_id11_4_,
            member0_.name as name9_4_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.id as id1_8_0_,
        team0_.createdBy as createdB2_8_0_,
        team0_.createdDate as createdD3_8_0_,
        team0_.lastModifiedBy as lastModi4_8_0_,
        team0_.lastModifiedDate as lastModi5_8_0_,
        team0_.name as name6_8_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
Hibernate: 
    select
        team0_.id as id1_8_0_,
        team0_.createdBy as createdB2_8_0_,
        team0_.createdDate as createdD3_8_0_,
        team0_.lastModifiedBy as lastModi4_8_0_,
        team0_.lastModifiedDate as lastModi5_8_0_,
        team0_.name as name6_8_0_ 
    from
        Team team0_ 
    where
        team0_.id=?

결론

  • 실무에서는 가급적 LAZY 로딩 전략을 사용하자.
  • 실무에서 대부분 MEMBER와 TEAM을 함께 사용한다면 JPQL의 fetch join을 통해서 해당 시점에 한방 쿼리로 가져와서 사용하라.
  • @ManyToOne, @OneToOne과 같이 @XXXToOne 어노테이션들의 기본이 즉시 로딩(EAGER)이다.
  • @OneToMany, @ManyToMany는 기본이 지연 로딩(LAZY)이다.
반응형

+ Recent posts