Spring Boot

JPA "N+1" 문제와 해결법

sounglikane 2024. 11. 18. 21:03

JPA N+1 문제는 JPA나 Hibernate를 사용하여 데이터베이스에서 관련된 엔티티를 가져올 때 발생하는 일반적인 성능 문제입니다.

 

1. JPA N+1 문제란?

시나리오:

  • UserPost라는 두 개의 엔티티가 있다고 가정합니다. 여기서 User는 여러 개의 Post를 가질 수 있는 One-to-Many(1:N) 관계를 가지고 있습니다.
  • 모든 User와 각 User의 관련된 Post 데이터를 함께 조회하려고 합니다.

어떻게 발생할까요?

  1. 모든 User를 가져오기 위해 JPA는 1개의 쿼리를 실행합니다.
  2. 조회된 각 User에 대해 JPA는 해당 User의 관련된 Post를 가져오기 위해 추가 쿼리를 실행합니다.
  3. 만약 N명의 User가 있다면, 추가로 N개의 쿼리가 실행되어 총 N+1개의 쿼리가 실행됩니다.

영향:

이러한 동작은 데이터베이스 접근이 비효율적으로 이루어지며, 특히 대규모 데이터 세트를 처리할 때 심각한 성능 저하를 초래할 수 있습니다.

 

예시 코드

@Entity
@Table(name="users")
class User {
    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "user")
    private List<Post> posts;
}

@Entity
@Table(name="posts")
class Post {
    @Id
    private Long id;

    private String title;

    @ManyToOne
    private User user;
}

 

Query

List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();
for (User user : users) {
    System.out.println(user.getPosts());
}

 

생성된 SQL

1- 모든 사용자들을 가져오기 (1 query)

SELECT * FROM user;

 

2- 각 사용자에 대한 게시물을 가져오기 (N queries)

SELECT * FROM posts WHERE user_id = ?;

 

N+1 문제가 발생합니다.

 

2. 해결법

'Fetch Join'을 사용하여 관련 엔터티를 명시적으로 결합하도록 쿼리를 수정합니다.

 

Query

List<User> users = entityManager
    .createQuery("SELECT u FROM User u JOIN FETCH u.posts", User.class)
    .getResultList();

 

생성된 SQL

SELECT u.*, p.* FROM user u LEFT JOIN post p ON u.id = p.user_id;