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 연관관계에서 데이터가 중복되거나 데이터의 크기가 곱이 되면서 가장 뚜렷하게 나타난다.
하지만 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능하다.
'◼ JPA' 카테고리의 다른 글
JpaRepository를 상속한 인터페이스가 구현체가 없이 동작하는 이유 (0) | 2023.06.19 |
---|---|
[JPA] @Modifying이란? 그리고 주의할점 (벌크 연산) (0) | 2023.06.12 |
[JPA] 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading) (0) | 2023.06.12 |
[JPA] 사용자 정의 쿼리 작성과 파라미터 바인딩(@Query, @Param) (0) | 2023.06.09 |
[JPA] JPQL이란? 사용방법, 기본 문법 총 정리 (1) | 2023.06.09 |