728x90

JPA 의 N+1 이란?

JPA(N+1) 문제는 JPA(Java Persistence API)에서 발생할 수 있는 성능 문제 중 하나입니다. 이 문제는 연관 관계가 있는 엔티티를 쿼리할 때 발생할 수 있습니다.

예를 들어, 하나의 엔티티 A와 그와 관계된 다수의 엔티티 B가 있을 경우, A와 B를 함께 조회하는 쿼리를 실행하면 A와 관련된 모든 B 엔티티를 가져옵니다. 하지만 이후에 B 엔티티를 사용하는 다른 코드에서는 각 B 엔티티마다 추가적인 쿼리가 실행됩니다. 이 때문에 총 N+1번의 쿼리가 실행되어 성능 문제가 발생할 수 있습니다.

이러한 문제를 해결하기 위해서는 JPA에서 제공하는 fetch 조인이나 엔티티 그래프 기능을 사용하면 됩니다. 이를 통해 한 번의 쿼리로 모든 관련 엔티티를 함께 가져올 수 있습니다. 또한, 지연 로딩을 사용하여 필요한 시점에만 엔티티를 가져오도록 설정할 수도 있습니다. 이러한 방법을 사용하면 JPA(N+1) 문제를 해결할 수 있습니다.

어떤 경우 발생하는지? 아뉘?

JPA는 객체-관계 매핑(Object-Relational Mapping, ORM)을 사용하는 프레임워크로, 객체와 데이터베이스 간의 불일치를 해결하기 위해 여러 기술을 제공한다. 하지만 JPA를 사용하다보면 N+1 문제가 발생할 수 있다. N+1 문제란, 쿼리 한 번으로 가져와야 할 데이터를 N 번 쿼리하는 문제를 말한다.

예를 들어, 게시글 리스트를 가져올 때 해당 게시글 작성자의 정보를 함께 가져와야 한다면, 게시글을 한 번 조회하고 작성자 정보를 가져오기 위해 작성자별로 N 번 쿼리해야 할 수 있다. 이는 많은 데이터를 조회할 때 성능 저하를 일으킬 수 있다.

왜 발생하는지

N+1 문제는 JPA의 지연 로딩(Lazy Loading)과 관련이 있다. JPA는 연관된 엔티티를 필요할 때 가져오는 지연 로딩 기능을 제공한다. 하지만 이때 연관된 엔티티가 많아지면, 필요한 엔티티를 가져오기 위해 N 번의 추가 쿼리가 발생하게 된다.

어떤 해결 방안이 있는지 ?

N+1 문제를 해결하는 방법은 크게 두 가지이다.

  • 첫 번째는 페치 조인(Fetch Join)을 사용하는 방법이다. 페치 조인은 연관된 엔티티를 함께 조회하는 방법으로, 쿼리 한 번으로 모든 데이터를 가져올 수 있다.
  • 두 번째는 엔티티 그래프(Entity Graph)를 사용하는 방법이다. 엔티티 그래프는 필요한 엔티티를 미리 지정해 놓는 방법으로, 페치 조인보다 더 세밀하게 데이터를 가져올 수 있다.

장단점?

페치 조인과 엔티티 그래프는 각각 장단점이 있다.

  • 페치 조인은 한 번의 쿼리로 모든 데이터를 가져올 수 있기 때문에 성능이 우수하다는 장점이 있다. 그러나 필요한 데이터만 가져오기 어렵기 때문에, 쿼리의 복잡성이 증가할 수 있다는 단점이 있다. 특히, 여러 개의 연관된 엔티티를 함께 가져와야 할 때는 쿼리의 복잡성이 급격히 증가할 수 있다.
  • 엔티티 그래프는 필요한 데이터만 가져올 수 있기 때문에, 페치 조인보다 더 세밀하게 데이터를 가져올 수 있다는 장점이 있다. 하지만 지정한 그래프만 가져오기 때문에, 모든 데이터를 가져오려면 여러 그래프를 지정해야 할 수 있다는 단점이 있다.

따라서 어떤 방법을 선택할지는 데이터의 복잡성과 성능 등 여러 요인을 고려해 결정해야 한다. 예를 들어, 데이터의 양이 많을 때는 페치 조인을 사용하면 쿼리의 복잡성이 증가하기 때문에 성능 저하가 발생할 수 있다. 그러나 필요한 데이터가 적을 경우에는 엔티티 그래프를 사용하면 불필요한 데이터를 가져오지 않아 성능이 개선될 수 있다.

예시 코드 살펴보자!

  • 페치 조인 예시
@Entity
public class Post {
    @Id
    private Long id;
    
    private String title;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private User author;
    
    // getters, setters
}

@Entity
public class User {
    @Id
    private Long id;
    
    private String username;
    
    // getters, setters
}

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    @Query("SELECT p FROM Post p JOIN FETCH p.author")
    List<Post> findAllWithAuthor();
}

위 코드에서 JOIN FETCH p.author 부분이 페치 조인을 사용한 부분이다. 이를 통해 게시글과 작성자를 함께 조회할 수 있다.

  • 엔티티 그래프 예시
@Entity
public class Post {
    @Id
    private Long id;
    
    private String title;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @EntityGraph(attributePaths = {"author"})
    private User author;
    
    // getters, setters
}

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    @EntityGraph(attributePaths = {"author"})
    List<Post> findAll();
}

위 코드에서 @EntityGraph 부분이 엔티티 그래프를 사용한 부분이다. attributePaths 속성을 사용해 연관된 엔티티를 미리 지정해 놓을 수 있다. 이를 통해 게시글과 작성자를 함께 조회할 수 있다.

결론적으로

JPA(N+1) 문제는 연관 관계가 있는 엔티티를 쿼리할 때 발생하는 성능 문제입니다. 이 문제를 해결하기 위해서는 fetch 조인이나 엔티티 그래프를 사용하여 한 번의 쿼리로 모든 관련 엔티티를 함께 가져오거나, 지연 로딩을 사용하여 필요한 시점에만 엔티티를 가져오도록 설정할 수 있습니다. 이를 통해 총 N+1번의 쿼리 실행을 줄여 성능 문제를 해결할 수 있습니다.

이 중 어떤 방법을 선택할지는 데이터의 복잡성과 성능 등 여러 요인을 고려해 결정해야 한다.


내저장소 바로가기 luxury515

+ Recent posts