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

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
profile

소소한개발팁

@개발자 뱅

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