java and sping

[Spring] JPA의 Fetch Join에 대해서?

코딩하는 공부방 2023. 5. 30. 03:57

안녕하세요
저번 시간에 실무에서 일어나는 문제 중에 제일 빈번하게 일어나는 N+1 문제에 대해서
정리를 해봤는데요.

 

[Spring] ORM(JPA, Django 등)에서의 제일 큰 N + 1 문제 ?(Fetch 조인)

안녕하세요 JPA를 공부하다가 가장 많은 문제가 발생하는 N + 1 문제에 대해서 설명해보겠습니다. N+1 문제란? 연관 관게에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된

nosechild.tistory.com

이번 포스팅에서는 N+1문제를 해결할 수 있는  Fetch Join에 대해서 알아보겠습니다.


Fetch Join(페치 조인)

Fetch Join은 SQL에서 말하는 조인의 종류가 아닙니다. JPQL에서 성능 최적화를 위해 제공하는 조인의 종류입니다.

※JPQL(Java Persistence Query Language)※
SQL이 DB에 있는 테이블을 조회하는 쿼리라고 한다면 JPQL은 엔티티 객체를 조회하는 객체지향 쿼리를 의미합니다. 문법은 SQL과 비슷하고 SQL이 제공하는 기능을 유사하게 지원합니다.

위 코드를 보시면 SQL 문법과 비슷한 형태로 쿼리를 생성하는 것을 확인할 수 있습니다. 또한 JPQL은 엔티티 직접 조회, 묵시적 조인, 다형성 지원, DTO로 조회 등의 기능을 제공하기에 SQL보다 간결하다는 장점을 가지고 있습니다.

Fetch Join을 사용하는 이유?

즉시로딩(fetch=FetchType.EAGER)은 매우 매력적입니다. 연관된 엔티티를 추가 SQL로 조회할 필요 없이 모두 가져올 수 있기 때문입니다. 하지만 문제점이 있습니다.

  1. 엔티티를 조회하지만, 연관된 엔티티는 필요하지 않아도 조회를 하기 때문에 성능상 이슈가 있습니다.
  2. 사용하지 않는 연관된 엔티티르 찾아오기 위해서 개발자가 의도하지 않은 Query가 발생하여 N+1문제가 발생할 수 있습니다.

따라서 성능상 이슈와 N+1 문제로 인해서 지연 로딩사용합니다. 그렇다면 의문이 들 수가 있다. 지연로딩일 때 연관된 엔티티를 사용할 때 어차피 한 번의 Query가 더 발생하는 부분입니다. 이때 Fetch Join 진가가 발휘됩니다.

 

MemberRepository Query 예

MemberTeamN:1 관계로 양방향으로 지연로딩(FetchType.Lazy)으로 연관관계 맺어져 있습니다. 

 

Fetch Join 사용안 한 예

JPQL의 SQL을 보시면 LAZY 지연로딩으로 Member만 조회하고 조인했던 Team은 전혀 조회가 안 되는 것을 확인을 할 수가 있습니다.
따라서 Member 엔티티만 조회를 해오고, 지연 로딩으로 설정되어 있어서 Team 엔티티는 프록시 객체로 가져오는 것을 확인할 수 있습니다.
이후 Team 엔티티의 정보를 조회하면 Team 엔티티의 정보를 위한 Select 쿼리가 데이터베이스에서 따로 나갑니다. 이것이 N+1 문제입니다.

 

Fetch Join 사용 한 예

<Team 엔티티>

Select 문을 보시면, 이번에는 Fetch Join을 사용해서 Member엔티티를 조회하면서 연관된 Team 엔티티도 함께 조회하는 것을 확인할 수가 있습니다.
또한, Fetch Join 사용하지 않은 것에는 프록시 객체로 가져온 것을, 이번에는 Team 엔티티로 가져오는 것을 확인을 할 수 있습니다. Fetch Join지연로딩 전략(fetch = FetchType.LAZY) 보다 우선합니다.

이런 Fetch Join을 사용하면 SQL를 한 번으로 연관된 엔티티들이 함께 조회할 수 있어서 SQL 횟수를 줄여 성능을 최적화시킬 수 있습니다.

 

Fetch Join 특징과 한계

ⓐ 패치 조인 대상에는 별칭을 줄 수 없습니다.

  • JPA 표준에는 지원하지 않지만 하이버네이트를 포함한 몇몇 구현체들은 Fetch Join에 별칭을 지원하지만 별칭을 잘못 사용하면 연관된 데이터 수가 달라져서 데이터 무결성이 성립을 안 해서 사용하지 않는 것이 좋습니다.

ⓑ 둘 이상의 컬렉션을 Fetch Join 할 수가 없습니다.

ⓒ 컬렉션 Fetch Join을 하면 페이징 API(setFirestResult, setMaxResults)를 사용할 수 없습니다.

  • 컬렉션(일대 다)가 아닌 단일 값 연관 필드(일대일, 다대일)들은 Fetch Join을 사용해도 페이징 API를 사용할 수 있습니다.
  • 하이버네이트에서 컬렉션을 Fetch Join 하고 페이징 API를 사용하면 경고 로그를 뱉어서 페이징 API를 사용할 수 없게 합니다.

 

※참고※
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94/dashboard

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard