[스프린터스] N+1 문제

MSK·2022년 6월 22일
0
post-custom-banner

JPA에서의 N+1 문제

  • 1:N 관계 엔티티 클래스를 JPQL을 이용하여 조회할 때 내부에 존재하는 객체(엔티티)에 접근하기 위해 쿼리가 다시 발생하는 상황

JPA와 JPQL
JPQL은 Java Persistence Query Language의 약자로, DB 테이블이 아니라 엔티티의 객체를 대상으로 검색하는 객체 지향 쿼리

  • JPA와 JPQL 동작 특성 때문에 N+1 문제가 발생
  • JPQL의 기능으로 Fetch Join(패치 조인)이 있는데, SQL의 성능 최적화에 대부분 1차적으로 사용

When? 언제 발생하는건가요?

→ JPA Repository를 활용해 인터페이스 메소드를 호출 할 때(Read 시)

Who? 누가 발생시키나요?

→ 1:N 또는 N:1 관계를 가진 엔티티를 조회할 때 발생

How? 어떻게 하면 발생되나요?

→ JPA Fetch 전략(Fetch Type)이 EAGER 전략으로 데이터를 조회하는 경우

→ JPA Fetch 전략(Fetch Type)이 LAZY 전략으로, 전체 데이터를 가져온 이후 연관 관계인 하위 엔티티를 사용할 때 다시 조회하는 경우

Why? 왜 발생하나요?

→ JPA Repository로 find 시 실행하는 첫 쿼리에서 하위 엔티티까지 한 번에 가져오지 않고, 하위 엔티티를 사용할 때 추가로 조회하기 때문에.

→ JPQL은 기본적으로 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문에.

EAGER(즉시 로딩)인 경우

1) JPQL에서 만든 SQL을 통해 데이터를 조회

2) 이후 JPA에서 Fetch 전략을 가지고(여기서는 즉시 로딩) 해당 데이터의 연관 관계인 하위 엔티티들을 추가 조회(LAZY - 지연 로딩 발생)

3) 2) 과정으로 N+1 문제 발생함

LAZY(지연 로딩)인 경우

1) JPQL에서 만든 SQL을 통해 데이터를 조회

2) JPA에서 Fetch 전략을 가지지만, 여기서는 지연 로딩이기 때문에 추가 조회는 하지 않음

3) 하지만, 하위 엔티티를 가지고 작업하게 되면 추가 조회하기 때문에 결국 N+1 문제가 발생함

해결방안

  • Fetch join 사용 -> inner join으로 호출
@Query("select o from Owner o join fetch o.cats")
List<Owner> findAllJoinFetch();
  • EntityGraph 사용 -> outer join
@EntityGraph(attributePaths = "cats")
@Query("select o from Owner o")
List<Owner> findAllEntityGraph();
  • 위 두 방법의 주의 사항: 중복으로 조회가 되기 때문에 set이나 distinct 사용

  • BatchSize 사용(SQL의 IN절 설정)

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Owner {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @BatchSize(size=5)
    @OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
    private Set<Cat> cats = new LinkedHashSet<>();
}
  • QueryBuilder 사용(Mybatis, QueryDSL, JOOQ, JDBC Templat)
// QueryDSL로 구현한 예제
return from(owner).leftJoin(owner.cats, cat)
                   .fetchJoin()

참고자료

profile
여긴어디나는누구
post-custom-banner

0개의 댓글