1. 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 쿼리 문제가 발생하는 상황은 다음과 같습니다
<java />
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만을 가져옵니다.
<java />
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 쿼리 문제는 데이터베이스에 대한 많은 불필요한 쿼리를 발생시키며, 성능 저하와 데이터베이스 부하를 초래할 수 있습니다.
해결하기 위한 방법은 다음과 같습니다:
<java />
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 쿼리에서 조인을 사용하여 연관된 엔터티를 함께 검색할 수 있습니다. 이로 인해 데이터베이스에서 필요한 데이터를 한 번에 가져올 수 있습니다.
<java />
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 |