JVM/JPA

JPQL 조인과 페치조인

kyoulho 2023. 6. 25. 22:27

조인

// 내부 조인
SELECT m FROM Member m JOIN m.team t
List<Member> members = em.createQuery(query, Member.class).getResultList();


// 외부 조인
SELECT m FROM Member m LEFT JOIN m.team t
List<Member> members = em.createQuery(query, Member.class).getResultList();


// 컬렉션 조인
SELECT t, m FROM Team t LEFT JOIN t.members m
List<Member> members = em.createQuery(query, Member.class).getResultList();


// 세타 조인(전혀 관계 없는 테이블들끼리의 조인)
// JPQL
SELECT COUNT(m) FROM Member m, Team t
WHERE m.username = t.name

// SQL
SELECT COUNT(m.ID)
FROM MEMBER M CROSS JOIN TEAM T
WHERE M.USERNAME=T.NAME
  

// JOIN ON(JPA 2.1 부터 지원)
// JPQL
SELECT m,t FROM Member m
LEFT JOIN m.team t on t.name = 'A'

// SQL
SELECT m.*, t.* FROM MEMBER m
LEFT JOIN TEAM t ON m.TEAM_ID = t.ID AND t.NAME='A'

 

페치 조인

JPQL에서 성능 최적화를 위해 제공하는 조인으로 연관된 엔티티나 컬렉션을 한 번에 같이 조회할 수 있다.

엔티티가 가진 모양이 아닌 전혀 다른 결과(통계)를 내야 한다면 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 효과적이다.

// 엔티티 페치 조인
// JPQL
SELECT m FROM Member m JOIN FETCH m.team
//SQL
SELECT m.*, t.*
FROM MEMBER m
INNNER JOIN TEAM t ON m.TEAM = t.ID

 

컬렉션 페치 조인

String jpql = "SELECT t FROM Team t JOIN FETCH t.members WHERE t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();

일대다(Team : Member) 관계라면 해당 List 에는 똑같은 엔티티가 중복되어 있을 수 있다.

쿼리 결과가 여러 로우일 수도 있기 때문인데 그럴땐 DISTINCT로 중복된 엔티티를 제거해야 할 필요가 있다.

 

 

페치 조인과 DISTINCT

SELECT DISTINCT t
FROM Team t JOIN FETCH t.members
WHERE t.name = '팀A'

DISTINCT를 사용하면 SQL에 SELECT DISTINCT가 추가되고 애플리케이션에서 DISTINCT 명령어를 보고 중복된 데이터를 걸러낸다. 따라서 하나만 조회된다.

 

주의

  • 페치 조인 대상에는 별칭을 줄 수 없다.
    • 하이버네이트를 포함한 몇몇 구현체들은 페치 조인에 별칭을 지원한다. 하지만 잘못 사용하면 연관된 데이터가 달라져서 데이터 무결성이 깨질 수 있다. 특히 2차 캐시와 함께 사용 시 조심해야 하는데, 연관된 데이터 수가 달라진 상태에서 2차 캐시에 저장되면 다른 곳에서 조회할 때도 연관된 데이터 수가 달라지는 문제가 발생할 수 있다.
  • 둘 이상의 컬렉션을 페치할 수 없다.
    • 구현체에 따라 되기도 하는데 컬렉션 * 컬렉션 카테시안 곱이 만들어지므로 주의해야 한다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    • 일대다가 아닌 단일 값 연관 필드들은 페치 조인을 사용해도 페이징 API를 사용할 수 있다.
    • 하이버네이트에서는 경고 로그를 남기며 메모리에서 페이징 처리한다. 성능 이슈와 메모리 초과 예외가 발생할 수 있다.

 

경로 표현식 (묵시적 조인)

조인은 성능상 차지하는 부분이 아주 크다. 성능이 중요하면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자.

 

단일 값 연관 경로 탐색

단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부 조인이 일어나는데 이것을 묵시적 조인이라 한다.

// JPQL
SELECT o.member FROM Order o

// SQL
SELECT m.*
FROM Orders o
JOIN Member m ON o.member_id=m.id

// JPQL
SELECT o.member.team
FROM Order m
WHERE o.product.name = 'productA' and o.address.city = 'JINJU'

// SQL
SELECT t.*
FROM Orders o
INNER JOIN Member m ON o.member_i=m.id
INNER JOIN Team t ON m.team_id=t.id
INNER JOIN Product p ON o.product_id=p.id
WHERE p.name = 'productA' and o.city='JINJU'

 

컬렉션 값 연관 경로 탐색

JPQL을 다루면서 많이 하는 실수 중 하나는 컬렉션 값에서 경로 탐색을 시도하는 것이다.

// 실패
select t.members.username
from Team t

// 조인을 사용
select m.username
from Team t
join t.members m

 

경로 탐색을 사용한 묵시적 조인 시 주의사항

  • 항상 내부 조인이다. 결과가 없을 수 있다는 뜻이다.
  • 컬렉션은 경로 탐색의 끝이다. 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다.
  • 경로 탐색은 주로 SELECT, WHERE 절(다른 곳에서도 사용됨)에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 준다.

 

 

'JVM > JPA' 카테고리의 다른 글

JPQL 조건식  (0) 2023.06.28
JPQL 서브쿼리  (0) 2023.06.26
JPQL 기초  (0) 2023.06.25
값 타입 컬렉션 @ElementCollection @CollictionTable  (0) 2023.06.25
임베디드 타입(복합 값 타입) @Embedded, @Embeddable  (0) 2023.06.24