N + 1 문제란? 발생 이유와 이를 해결하는 방법을 설명해주세요. #150
Replies: 2 comments
-
문제 설명N + 1 문제는 데이터베이스에서 발생하는 성능 문제 중 하나로, 데이터 조회 시 관련된 엔티티의 수에 비례하여 추가적인 쿼리가 발생하는 현상을 의미합니다. 이 문제는 데이터베이스 성능 저하의 주요 원인 중 하나이며, 쿼리의 실행 횟수가 많아질수록 데이터베이스에 부하를 줄 수 있습니다.
해결 방법Join Fetch부모 엔티티를 조회할 때 자식 엔티티를 함께 가져옵니다. @Query("select p from Post p join fetch p.comments")
List<Post> findAllJoinFetch(); EntityGraph엔티티 그래프를 사용하여 엔티티를 조회할 때 연관된 엔티티를 함께 가져옵니다. @EntityGraph(attributePaths = "comments")
@Query("select p from Post p")
List<Post> findAllEntityGraph(); 주의사항
|
Beta Was this translation helpful? Give feedback.
-
JPA N+1 문제는 연관 관계가 설정된 엔티티를 조회할 경우에, 조회된 데이터 개수(N)만큼 연관관계의 조회 쿼리가 추가로 발생하는 현상입니다. 예를 들어, 블로그 게시글과 댓글이 있는 경우, 게시글을 조회한 후 각 게시글마다 댓글을 조회하기 위해 추가 쿼리가 발생한다면 N + 1 문제가 발생한 것입니다. 게시글이 10개라면 총 11개의 쿼리(게시글 조회 1개 + 각 게시글의 댓글 조회 10개)가 실행됩니다. N+1 문제가 발생할 수 있는 상황은 다음과 같습니다. 예시에서 User와 Article이 1:N 양방향 연관관계를 갖고 있도록 설정했습니다. 1. 글로벌 패치 전략 - 즉시로딩 글로벌 패치 전략을 즉시로딩으로 설정하고 findAll()을 실행하면 N+1 문제가 발생합니다. 이는 findAll()은 // User.java
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<Article> articles = emptySet();
// Article.java
@ManyToOne(fetch = FetchType.EAGER)
private User user;
// Test.java
@Test
@DisplayName("Eager type은 User를 전체 검색할 때 N+1문제가 발생한다.")
void userFindTest() {
System.out.println("== start ==");
List<User> users = userRepository.findAll();
System.out.println("== find all ==");
} 2. 글로벌 패치 전략 - 지연로딩 글로벌 패치 전략을 지연로딩으로 하고 findAll()을 실행하면 N+1 문제가 발생하지 않습니다. 이는 연관관계에 있는 엔티티를 실제 객체 대신에 프록시 객체로 생성하여 주입하기 때문입니다. 하지만 프록시 객체를 사용할 경우에 실제 데이터가 필요하여 조회하는 쿼리가 발생하고 N+1문제가 발생합니다. // User.java
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY) // 변경
private Set<Article> articles = emptySet();
// Test.java
@Test
@DisplayName("Lazy type은 User 검색 후 필드 검색을 할 때 N+1문제가 발생한다.")
void userFindTest() {
System.out.println("== start ==");
List<User> users = userRepository.findAll();
System.out.println("== find all ==");
for (User user : users) {
System.out.println(user.articles().size());
}
} N+1 문제를 해결하는 방법은 다음과 같습니다. 1. fetch join fetch join은 연관관계에 있는 엔티티를 한번에 즉시로딩하는 구문입니다. @Query("select distinct u from User u left join fetch u.articles")
List<User> findAllJPQLFetch(); 2. jpql에서 fetch join을 하게 된다면 하드코딩을 하게 된다는 단점이 있습니다. 이를 최소화하고 싶다면 @entitygraph를 사용할 수 있습니다. @EntityGraph(attributePaths = {"articles"}, type = EntityGraphType.FETCH)
@Query("select distinct u from User u left join u.articles")
List<User> findAllEntityGraph(); |
Beta Was this translation helpful? Give feedback.
-
.
Beta Was this translation helpful? Give feedback.
All reactions