Application/JPA

[JPA] 지연 로딩 N+1 문제와, JPQL 페치 조인

반응형
JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다.

자바 ORM 표준 JPA 프로그래밍

페치 조인

페치(fetch) 조인은 SQL 조인의 종류가 아니라 JPQL에서 성능 최적화를 위해 제공하는 기능.

# JPQL
SELECT m FROM Member m JOIN FETCH m.team

# SQL
SELECT m.*, t.* 
FROM Member m
INNER JOIN Team t
ON m.team_id = t.id

페치 조인을 알아보기 전에 페치 조인을 사용해야 하는 상황을 먼저 보자.

지연 로딩과 N + 1 문제

글로벌 로딩 전략이 지연 로딩으로 설정되어 있고, (Lazy)

회원 1 - 팀 A, 회원 2, 팀 B, 회원 3, 팀 B라는 데이터가 있다고 가정하자.

String query = "select m From Member m";

List<Member> result = em.createQuery(query, Member.class).getResultList();

for (Member m : result) {
    sout(member.getName() + ", " + member.getTeam().getName());
}

SELECT m FROM Member m을 했으므로 멤버를 찾는 SQL이 나가고, Team은 프록시 객체를 반환한다.

N + 1 문제 설명

SELECT m FROM Member ~ # 멤버를 1회 조회하기 위해서

SELECT t FROM Team ~ 
SELECT t FROM Team ~ # 팀을 2번 호출 ( N + 1 문제 발생)

1. 회원1의 이름을 출력하고 나서, 회원이 속한 팀 A를 조회하는 쿼리가 나간다.

왜냐하면? 팀은 프록시 객체이기 때문이다. 

 

2. 또한 회원2의회원 2의 이름을 출력하고 나서, 회원 2의 팀 정보가 프록시이고, 현재 영속성 컨텍스트의 1차 캐시에 존재하지 않으므로 다시 쿼리가 나간다.

 

3. 회원3의 경우는 영속성 컨텍스트의 1차 캐시에 팀 B의 정보가 저장되어 있어서 SQL이 나가진 않는다.

 

이 처럼 1번의 멤버를 조회하는 쿼리 + N개의 팀을 조회하는 쿼리 (멤버가 다 다른 팀일 경우에)가 나가는 문제가 발생한다.

이러한 문제를 N + 1 문제라고 한다.

 

여기서 프록시, 지연로딩이 헷갈리시면 아래의 글을 확인해주세요.
 

[JPA] 프록시란? 지연로딩 vs 즉시 로딩란?

JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다. 프록시란? 객체는 객체 그래프를 통해 연관된 객체들을 탐색한다. 그런데 객체가 데이터베이스에 저장되어 있으므로 연관된 객체를

willseungh0.tistory.com

 


'페치 조인이란?

연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이다.
(위의 경우 멤버를 조회할때 팀도 한 번에 다 같이 조회해서 N번의 팀을 조회하는 쿼리가 나가지 않아도 된다)

 

페치 조인의 효과

페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 있어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있다. (N+1 문제를 해결)

 

엔티티에 직접 적용하는 로딩 전략은 애플리케이션 전체에 영향을 미치므로 글로벌 로딩 전략이라고 부른다.

@ManyToOne(fetch = FetchType.EAGER)

페치 조인은 글로벌 로딩 전략보다 우선순위가 높아서 글로벌 로딩 전략을 LAZY (지연 로딩)으로 설정해도 JPQL에서 페치 조인을 사용하면 페치 조인을 적용해서 함께 적용한다.

 

아니 그냥 글로벌 로딩 전략을 즉시 로딩으로 바꾸면 되지 않나요?

최적화를 위해 글로벌 로딩 전략을 즉시 로딩으로 설정하면 애플리케이션 전체에서 항상 즉시 로딩이 일어난다.

물론 일부는 빠를 수 있지만, 전체로 보면 사용하지 않는 엔티티를 자주 로딩하므로 오히려 성능에 악영향을 미칠 수 있다.

따라서 글로벌 로딩 전략은 가능하면 지연 로딩을 사용하고, 최적화가 필요하면 페치 조인을 적용하는 것이 효과적이다.

반응형