[JPA] JPQL의 fetch join(패치 조인)이란?

Fetch Join

JPQL에서 성능 최적화를 위해 제공하는 기능으로 연관된 엔티티나 컬렉션들을 한번의 SQL 쿼리로 함께 조회할 수 있다.

연관된 엔티티에 대해 추가적인 쿼리를 실행할 필요 없이 효율적인 로드를 할 수 있는 것이다.

즉, 패치 조인은 성능 최적화에 주로 사용되며, N+1 문제를 해결하는 데 효과적이다.

 

예를 들어 Order와 Product 엔티티가 있고, 이 둘이 (1 : N) 일대다 연관관계가 있다고 가정해보자.

Order 엔티티를 조회하면서 관련된 상품(Product) 엔티티도 한 번의 쿼리로 함께 조회하기 위해 패치 조인을 사용할 수 있다.

SELECT o FROM Order o JOIN FETCH o.products

위의 쿼리는 패치 조인을 사용하여 Order 엔티티와 관련된 Product 엔티티를 함께 조회한다.

이렇게 하면 각 주문과 관련된 상품을 별도의 쿼리로 실행하는 것보다 효율적으로 데이터를 로드할 수 있다.

 

주의할점

패치 조인을 사용할 때 일대다(OnetoMany) 또는 다대다(ManytoMany) 관계의 엔티티가 많이 포함되면, 불필요한 중복 데이터를 가져오는 일이 발생할 수 있다.

이 경우, DISTINCT 키워드를 사용하여 중복 데이터를 제거할 수 있다.

 


일반 Join 과 Fetch Join의 차이

일반 Join과 Fetch Join의 차이는 데이터를 조회할 때 어떻게 가져오느냐에 있다.

 

일반 Join

일반 Join은 데이터베이스 테이블을 조인하여 연관된 데이터만 가져온다.  하지만 가져온 데이터를 사용하려고 할 때 별도의 쿼리를 수행해야 한다.

예를 들어, 주문(Order)과 상품(Product)이 있는 쇼핑몰에서, 특정 주문의 상품을 알고 싶다면,  일반 Join에서는 주문과 상품 테이블을 조인하여 상품 정보를 가져온다.

SELECT o, p FROM Order o JOIN o.products p WHERE o.id = :orderId

주문에 대한 상품 정보를 받아왔지만, 해당 상품 데이터를 실제로 사용하려면 별도의 쿼리를 수행해야 한다.

 

Fetch Join

조회의 주체가 되는 엔티티와 그 관련 엔티티들 까지 함께 조회한다.

이를 객체 그래프 로드라고 하는데, 즉시 원하는 정보를 사용할 수 있도록 데이터를 로드해온다.

이 방식을 통해 한 번의 쿼리로 필요한 정보를 모두 가져와서 성능을 향상시킬 수 있다.

위 예시에서 Fetch Join을 사용하면 주문과 해당 주문에 포함된 상품 데이터를 한번에 가져와 별도의 쿼리 없이 바로 상품 정보에 접근할 수 있다.

SELECT o FROM Order o JOIN FETCH o.products WHERE o.id = :orderId

 


언제 패치 조인을 사용하는 것이 좋을까?

연관 엔티티의 데이터를 자주 사용하는 경우

지연 로딩의 경우 연관 엔티티를 자주 가져오는 경우에 성능 저하가 발생할 수 있다.

이 때, 연관 엔티티의 데이터를 함께 가져오면 지연 로딩에 따른 추가 쿼리를 줄이고 성능을 향상시킬 수 있다.

 

즉, A만 필요할 때는 FetchType.Lazy 지연 로딩으로 A 만 불러 사용하고, 

A와 B를 같이 부르고 싶을 경우에는 FetchType.Lazy  + fetch join 조합을 사용하여 성능을 향상 시킬 수 있다.

 

n+1 문제가 발생하는 경우

N+1 문제는 연관 엔티티가 실제 사용될 때마다 별도의 쿼리가 발생하여 성능 이슈를 초래하는 것이다.

이 경우, 추가적인 쿼리가 불필요하게 발생하여 성능 저하가 발생한다.

fetch join은 연관 엔티티와 함께 즉시 로딩되므로, n+1 문제를 제거할 수 있다.


Fetch Join의 한계

Fetch Join에 선언된 엔티티에 대해서 별칭 불가

별칭을 사용하면 결과 열과 그 이름을 바뀔 수 있다.

이 경우 JPA는 연관된 엔티티 대신 별칭을 사용하여 결과를 리턴한다.  이로 인해 예기치 않은 결과를 리턴할 수 있다.

하지만 일관성을 해치지 않는 경우에는 별칭을 사용해도 문제가 되지 않는다.

그리고 일관성이 깨져도 조회용도로만 사용하면 크게 문제는 되지 않는다고 한다.

 

추가로, 패치 조인 대상에는 별칭을 줄 수 없기에 SELECT, WHERE, 서브 쿼리에 패치 조인 대상을 사용할 수 없다.

 

둘 이상 컬렉션을 Fetch Join 불가

둘 이상의 컬렉션에 대해 Fetch Join을 사용하면, 결과 데이터의 크기가 곱셈 원리로 인해 급증하게 된다.

 

컬렉션을 fetch join 시 페이징 기능 사용 제한

페이징 기능을 사용하여 데이터를 검색할 때, 일반적으로 데이터베이스에서 필요한 범위의 데이터만 가져온 후 이를 화면에 표시한다.

그러나 Fetch Join을 사용할 경우 서로 관련된 데이터를 함께 로드하게 되며, 이 때문에 페이징 처리가 제대로 이루어지지 않을 수 있다.

이 문제는 OneToMany와 ManyToMany 연관관계에서 데이터가 중복되거나 데이터의 크기가 곱이 되면서 가장 뚜렷하게 나타난다.

 

하지만 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능하다.