김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
https://www.inflearn.com/courses?s=%EA%B9%80%EC%98%81%ED%95%9C
JPQL - 페치 조인(fetch join)
• SQL 조인 종류X
• JPQL에서 성능 최적화를 위해 제공하는 기능
• 연관된 엔티티나 컬렉션을 SQL한 번으로 함께 조회하는 기능
• join fetch 명령어 사용
• 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
엔티티 페치 조인
• 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에) [JPQL]
• SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
• [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
(M.*, T,*은 각각 M에 대한 모든 것과, T에 대한 모든 것.)
패치조인 코드로 보기 :
위와 같은 상황일 때, 조인을 하면 아래와 같은 결과가 나올 것입니다.
영속성 컨텍스트는 아래와 같습니다.
회원1과 회원2는 같은 팀을 참조합니다.
이제 코드로 보겠습니다. 먼저 위와 같은 상황의 데이터를 셋하겠습니다.
이제 아래와 같은 코드를 실행해보겠습니다. (모든 멤버를 조회하고, 멤버의 이름과, 멤버가 속한 팀의 이름을 조회하는 코드입니다.)
결과는 아래와 같습니다.
실행코드의 for문을 순서대로 정리하면 아래와 같습니다.
1. em.craeteQuery에 의해 멤버들의 정보를 영속성 컨텍스트에 담고, 컬렉션에 담습니다. 영속성 컨텍스트에서 멤버1의 이름을 조회합니다. 그런데 현재 멤버와 팀은 지연로딩 전략이므로, 팀에 대해서는 조인하여 가져오지 않습니다.
2. 멤버1의 username을 조회했는데, 멤버가 속한 팀의 이름까지 조회해야합니다. 하여 멤버1이 속한 팀A에 대한 select를 날립니다.
3. 이제 멤버2 차례입니다. 멤버들에 관한 정보는 이미 영속성 컨텍스트에 있습니다. 그런데 멤버2가 속한 팀A가 이미 영속성 컨텍스트에 있으므로, 팀에 대한 select를 날릴 필요가 없습니다.
4. 멤버3의 username은 영속성 컨텍스트에 있고, 멤버3이 속한 팀B에 대한 정보는 DB에 있으므로, select문을 날려야합니다.
5. 멤버4는 팀이 없어서 member.getTeam()에서 널포인터익셉션 에러가 발생합니다.
N+1 문제 발생: 그런데 위와 같이 설계한 경우 만약 회원100명을 조회한다면 모두 팀이 다를경우 100번의 select쿼리가 추가적으로 나갑니다.
위와 같은 문제를 해결하기 위해서는 Fetch join을 사용합니다.
위와 같이 하면 팀까지 한번에 같이 가져옵니다.
결과에서 보이듯이 한번에 팀까지 가져온 것을 볼 수 있습니다.
컬렉션 패치조인 코드로 보기 :
반대로 팀에 있는 members를 조인합니다.
결과를 보시면 팀A가 중복되어 출력됩니다.
이처럼 중복되는 이유는 아래와 같습니다.
위에서 보시다시피 팀A는 member와 조인을 위해 아래와 같은 테이블이 만들어 집니다.
영속성 컨텍스트에는 같은 주소 값을 가진 결과가 담기게 됩니다.
팀에 있는 멤버들을 출력해보겠습니다.
결과:
중복을 확인할 수 있습니다.
다음을 보시면 중복이 발생하는 것을 더 쉽게 이해하실 수 있습니다.
위와 같은 쿼리를 실행하면 result의 사이즈는 3이 나옵니다.
하지만 아래와 같이 join을 하지 않고 조회한다면?
물론 결과는 현재 팀이 팀A와 팀B 2개가 있으므로, 2가 나올 것입니다.
즉 join을 하면 데이터가 뻥튀기 되어 중복 문제가 발생합니다.
해결방안 : DISTINCT
이와 같은 문제를 해결하기 위해 DISTINCT를 사용합니다.
패치 조인과 DISTINCT
( SQL의 DISTINCT는 중복된 결과를 제거하는 명령 )
JPQL의 DISTINCT 2가지 기능 제공 :
1. SQL에 DISTINCT를 추가
2. 애플리케이션에서 엔티티 중복 제거
만약 SQL의 DISTINCT만을 사용하면 위와 같은 상황에서 중복이 제거되지 않습니다.
왜냐하면 SQL의 DISTINCT는 join테이블에서 완전히 같은 중복만을 제거해주기 때문입니다.
아래 테이블에서 보시다시피 멤버의 이름, ID가 모두 다릅니다.
하여 JPA의 DISTINCT는 다음과 같은 시도를 해줍니다.
• DISTINCT가 추가로 애플리케이션에서 중복 제거시도
• 같은 식별자를 가진 Team 엔티티 제거
이제 DISTINCT를 적용한 코드와 결과를 보겠습니다.
결과:
중복이 제거된 것을 보실 수 있습니다.
페치 조인과 일반 조인의 차이
일반 조인 : 실행시 연관된 엔티티를 함께 조회하지 않습니다.
즉 Team을 가져올 시에 memebers는 아직 초기화 되지 않았습니다.
하여 members에 접근할 시 select를 다시 날려야 합니다.
패치 조인: 패치조인은 연관된 엔티티를 함께 조회합니다.
'JPA > JPQL' 카테고리의 다른 글
JPQL 다형성 쿼리 (0) | 2021.07.24 |
---|---|
JPQL - 패치조인의 특징과 한계 (0) | 2021.07.24 |
JPQL 경로 표현식 (0) | 2021.07.22 |
JPQL 기본함수 (0) | 2021.07.21 |
JPQL 조건식(CASE 등) (0) | 2021.07.21 |