소소한개발팁
article thumbnail
Published 2023. 9. 12. 18:29
JPA - N+1 문제란? 컴퓨터 언어/Java
반응형

N+1 문제란 ? 

N+1 쿼리 문제(N+1 Query Problem)는 객체 관계 매핑(Object-Relational Mapping, ORM) 라이브러리와 관련된 성능 이슈 중 하나로, 데이터베이스와의 효율적인 데이터 검색 및 로딩을 방해하는 문제입니다. 이 문제는 다음과 같은 상황에서 발생합니다.

 

일대다 또는 다대일 관계(One-to-Many 또는 Many-to-One Relationship): 예를 들어, 한 명의 고객(Customer)가 여러 개의 주문(Order)을 가질 수 있는 경우 또는 여러 주문이 한 명의 고객에게 연결된 경우와 같이 일대다 또는 다대일 관계가 있는 엔터티 간의 관계입니다. 

 

지연 로딩(Lazy Loading)이 설정된 경우: 많은 ORM 라이브러리에서는 기본적으로 연관된 엔터티를 지연 로딩하는 설정을 사용합니다. 이는 연관된 엔터티가 실제로 필요할 때만 데이터베이스에서 가져오도록 하는 것을 의미합니다.

 

 

N+1 쿼리 문제가 발생하는 상황은 다음과 같습니다

 

import javax.persistence.*;
import java.util.List;

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private List<Order> orders;

    // Getter와 Setter 생략
}

첫 번째 쿼리(N): 주로 상위 엔터티(예: 고객)를 검색하는 쿼리가 실행됩니다. 이 쿼리에서는 연관된 엔터티(예: 주문)의 ID만을 가져옵니다.

 

import javax.persistence.*;

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderNumber;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // Getter와 Setter 생략
}

N개의 추가 쿼리(1씩 총 N번): 이후에 상위 엔터티(고객)를 순회하면서 각 연관된 엔터티(주문)를 가져오려고 할 때, 각 주문에 대한 별도의 쿼리가 실행됩니다. 이때, N개의 추가 쿼리가 발생하므로 N+1번의 쿼리가 실행됩니다. 이것이 N+1 쿼리 문제입니다. N+1 쿼리 문제는 데이터베이스에 대한 많은 불필요한 쿼리를 발생시키며, 성능 저하와 데이터베이스 부하를 초래할 수 있습니다.

 

해결하기 위한 방법은 다음과 같습니다:

 

 

반응형

 

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("your-persistence-unit-name");
        EntityManager entityManager = entityManagerFactory.createEntityManager();

        // 트랜잭션 시작
        entityManager.getTransaction().begin();

        // LEFT JOIN FETCH를 사용하여 고객과 관련된 주문을 한 번에 가져오기
        String jpql = "SELECT c FROM Customer c LEFT JOIN FETCH c.orders WHERE c.id = :customerId";
        Customer customer = entityManager.createQuery(jpql, Customer.class)
                .setParameter("customerId", 1L)
                .getSingleResult();

        // 고객의 주문 목록 조회
        List<Order> orders = customer.getOrders();

        // 각 주문 출력
        for (Order order : orders) {
            System.out.println("Order Number: " + order.getOrderNumber());
        }

        // 트랜잭션 커밋
        entityManager.getTransaction().commit();

        // 엔티티 매니저 종료
        entityManager.close();
        entityManagerFactory.close();
    }
}

조인 사용: JPQL 쿼리에서 조인을 사용하여 연관된 엔터티를 함께 검색할 수 있습니다. 이로 인해 데이터베이스에서 필요한 데이터를 한 번에 가져올 수 있습니다.

 

import javax.persistence.*;
import java.util.List;

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private List<Order> orders;

    // Getter와 Setter 생략
}

import javax.persistence.*;

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderNumber;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // Getter와 Setter 생략
}

Batch Fetching 설정: ORM 라이브러리에서는 배치 패치(Batch Fetching) 설정을 통해 N+1 쿼리 문제를 해결할 수 있는 경우도 있습니다. 이를 통해 여러 개의 ID를 가진 연관 엔터티를 한 번에 가져올 수 있습니다.

반응형

'컴퓨터 언어 > Java' 카테고리의 다른 글

자바 버전 별 특징 정리 ( 8이후 )  (1) 2024.02.14
JPA - OSIV 패턴  (0) 2023.09.23
JPA - JPQL 사용 방법  (0) 2023.09.12
JPA - 값 타입 사용 방법  (0) 2023.09.11
JPA - Proxy(프록시) 이해하기  (0) 2023.09.07
profile

소소한개발팁

@개발자 뱅

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!