성능 최적화(1) - N+1

2024. 2. 7. 22:01spring/JPA

N+1

지연로딩과 N+1

JPA로 애플리케이션을 개발할 때 성능상 가장 주의해야 한다.

=============================회원 1=================================
@Entity
@Getter @Setter
public class Member {

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

    @Embedded
    private Address address;

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

=============================주문 N=================================
@Entity
@Table(name = "orders")
@SequenceGenerator(
        name = "ORDER_SEQ_GENERATOR",
        sequenceName = "ORDER_SEQ"
)
@Getter @Setter
public class Order {

    @Id @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "ORDER_SEQ_GENERATOR"
    )
    @Column(name = "order_id")
    private Long id;

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

 

 

지연 로딩으로 JPQL에서 쿼리를 가져오자

public List<Member> findAll() {
    return em.createQuery("SELECT m FROM Member m", Member.class)
            .getResultList();
}

 

 

지연 로딩이므로 데이터베이스에서 회원만 조회되므로 주문 컬렉션은 지연 로딩된다. 이후 비즈니스 로직에서 주문 컬렉션을 실제 사용할 때 지연 로딩이 발생한다.

public Member findMember(Long id) {
    Member member = memberRepository.findById(id);

    for (Order order : member.getOrders()) {
        log.info("id = {}", order.getId());
    }

    return member;
}

 

회원(1)의 주문 컬렉션(N)을 순회하면서 각 주문 엔티티를 초기화 할 때마다 쿼리를 날리므로 N+1 문제가 발생한다.

 

해결책1 - 페치 조인

페치 조인은 SQL 조인을 사용해서 연관된 엔티티를 함께 조회하므로 지연로딩이 발생하지 않아 N+1이 발생하지 않는다.

select m from Member m join fetch m.orders

 

 

해결책2 - 하이버네이트 @BatchSize

@BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 대 지정한 size만큼 SQL의 IN 절을 사용해서 조회한다.

만약 조회한 회원이 10명인데 size=5로 지정하면 2번의 SQL만 추가로 실행한다.

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

 

아래 예제에서 order.getId()를 조회할 때 한번에 5개씩 IN 절을 사용해서 불러온다.

public Member findMember(Long id) {
    Member member = memberRepository.findById(id);

    for (Order order : member.getOrders()) {
    	//이때!!!
        log.info("id = {}", order.getId());
    }

    return member;
}

 

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

성능 최적화(3) - 배치 처리  (0) 2024.02.13
성능 최적화(2) - 읽기 전용  (0) 2024.02.13
JPQL(3) - 페이징, 조인  (1) 2024.01.25
JPQL(2) - 프로젝션  (0) 2024.01.24
JPQL(1) - 기본 문법  (0) 2024.01.24