티스토리 뷰

 

들어가기 앞서, Proxy와 지연로딩의 개념을 알아야 합니다.

여기를 참고해 주세요!

N + 1 문제란?

JPQL을 사용하여 Entity를 조회할 때, 연관관계에 있는 Entity를 같이 조회할 때 발생하는 문제입니다. 연관관계에 있는 Entity를 즉시로딩(fetch = EAGER)로 조회하면 추가적인 쿼리문이 발생합니다. 즉, 조회된 Entity에 연관된 N개의 Entity를 조회하는 쿼리문이 N번 발생합니다.

 

예를 들어, Member Entity와 Order Entity가 존재합니다. Member Entity와 Order Entity는 1:N 관계라고 가정합시다.

Member Entity

@Entity
@Getter @Setter
public class Member {

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

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

 

 

Order Entity

@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {

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

    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "member_id")
    private Member member;
}

 

DB

 

JPQL을 사용하여 Order Entity를 조회해 보겠습니다.

@Test
void eagerTest() {
    List<Order> orders = em.createQuery("select o from Order o", Order.class)
            .getResultList();
}

아래와 같이 3번의 쿼리문이 발생합니다. 

즉시로딩 사용

 

예상했던 쿼리문은 1번이었는데 왜 3번의 쿼리문이 발생한 것일까요? JPQL을 사용하여 Entity를 조회하면 fetch = EAGER인 연관관계 Entity를 조회하는 추가 쿼리문이 발생하기 때문입니다. 

// JPQL
List<Order> orders = em.createQuery("select o from Order o", Order.class)
        .getResultList();
        
// SQL        
select * from Order
  • JPQL이 SQL로 변경되면서 Order 테이블 조회합니다. (쿼리문 1번 발생)
    • id가 4와 11인 2건이 조회됩니다.
  • Order 테이블과 매핑된 Order Entity를 확인합니다.
    • Order Entity에 Member Entity와 연관관계에 있음을 알게 됩니다.
  • 각 Order Entity마다 Member Entity를 조회하는 추가 쿼리문을 발생시킵니다.
    • id가 4인 Order Entity와 연관관계에 있는 id가 1인 Member Entity를 조회합니다. (쿼리문 1번 발생)
    • id가 11인 Order Entity와 연관관계에 있는 id가 8인 Member Entity를 조회합니다. (쿼리문 1번 발생)

따라서 총 3번의 쿼리문이 발생하게 됩니다. 즉시로딩을 사용하였기 때문에 Entity 조회 시에 연관관계에 있는 Entity를 같이 조회해야 합니다.

해결 방법

모든 연관관계에 지연 로딩(fetch = LAZY)을 사용하고, fetch join을 사용합니다.

지연로딩을 사용하여 필요한 Entity만 DB에서 조회하고, 연관관계에 있는 Entity도 같이 조회가 필요하다면 fetch join을 사용하여 쿼리문을 1번만 호출하여 조회합니다.

 

Order Entity
fetch = EAGER -> fetch = LAZY

@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {

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

    // @ManyToOne(fetch = EAGER)    
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}

 

일단 JPQL을 사용하여 지연 로딩으로 변경한 Order Entity를 조회해 봅시다. Order Entity와 연관관계에 있는 Member Entity의 클래스를 조회하니 Proxy 객체가 저장되어 있어 있습니다.

@Test
void eagerTest() {
    List<Order> orders = em.createQuery("select o from Order o", Order.class)
            .getResultList();
    System.out.println("orders.get(0).getMember().getClass() = " + orders.get(0).getMember().getClass());
}

프록시 객체

 

이번엔 fetch join을 사용한 JPQL을 사용해봅시다.

Order Enttiy와 연관관계에 있는 Member Entity를 쿼리문 1번으로 함께 조회하게 됩니다.

또한, Proxy 객체가 아닌 실제 객체를 전달받았음을 알 수 있습니다.

 

Total
Today
Yesterday
최근에 올라온 글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30