페치 조인 (fetch join)

연관된 엔티티나 컬렉션을 SQL 한번에 가져오고 싶다

다대일 관계인 Order과 Member 엔티티가 있다고 하자.
Order를 가져오면서 Member 정보도 같이 한번의 쿼리로 가져오고 싶을땐 어떻게 해야될까?

그렇다면 어떻게 해야하는가!
바로 fetch join을 사용하면된다.

em.createQuery("select o FROM Order o join fetch o.member m", Order.class).getResultList();
select m.*, 
       o.* 
from   orders o 
       join member m 
         on o.member_id = m.member_id 

Order와 연관된 Member까지 쿼리 한번으로 가져와서 Order 타입으로 받은걸 볼 수 있다.

EAGER & em.find()
fetch 전략을 EAGER로 한 상태에서 em.find()를 사용하면 단일 엔티티에 대하여 한번에 연관된 엔티티까지 가져올 수 있다.
하지만 EAGER를 사용해야되고 단일 조회만 가능하다.

  em.find(Order.class,1L);
  select m.*, 
         o.* 
  from   orders o 
         left join member m 
                on o.member_id = m.member_id 
  where o.order_id = 1

페치 조인이란

컬렉션 페치 조인

다대일뿐만 아니라 일대다도 페치 조인을 사용할 수 있다.

양방향 연관관계가 설정되어 있다 가정하고 Member를 가져올때 연관된 Order 목록들을 가져오도록 해보자

List<Member> members = em.createQuery("select m FROM Member m join fetch m.orders", Member.class).getResultList();
select m.*, 
       o.* 
from   member m 
       inner join orders o 
               on m.member_id = o.member_id 

데이터베이스에 날려진 SQL을 보면 이상없이 데이터를 가져온것 같지만 한가지 주의사항이 있다.
일대다 조인시에는 데이터가 부풀려지기 때문에 Member 컬렉션에 담는 과정에서 동일한 값이 들어갈 수 있다.
Member를 기준으로 조인을 걸게되면 아래 결과 테이블 처럼 연관된 Order 만큼 Member 튜플이 생성이 되는걸 볼 수 있다.
|id|name|id|member_id|name| |:—:|:—:|:—:|:—:|:—:| |1|회원1|1|1|주문1| |1|회원1|2|1|주문2|

for(Member member : members) {
 System.out.println(member.getName() + " " + member);
 for (Order order : member.getOrders()) {
 System.out.println(-> " + order.getName()+ " " + order);
}
/*
회원1 Member@0x100
-> 주문1 Order@0x200
-> 주문2 Order@0x300
회원1 Member@0x100
-> 주문1 Order@0x200
-> 주문2 Order@0x300
회원2 Member@0x400
-> 주문3 Order@0x500
-> 주문4 Order@0x600
*/

페치 조인과 DISTINCT

일대다 페치조인시 중복된 결과를 제거하기 위해 JPQL에서도 DISTINCT 명령어를 제공한다.
JPQL의 DISTINCT는 2가지 기능을 제공

  1. SQL에 DISTINCT를 추가
  2. 애플리케이션에서 엔티티 중복 제거
    List<Member> members = em.createQuery("select distinct m FROM Member m join fetch m.orders", Member.class).getResultList();
    
    select distinct m.*, 
                 o.* 
    from   member m 
        inner join orders o 
                on m.member_id = o.member_id 
    

    보면 SQL에 DISTINCT를 추가한다. 하지만 컬럼별로 데이터가 완전히 같지 않아서 중복제거에는 실패한다.
    그래서 애플리케이션에서 중복 제거를 시도한다.
    같은 식별자를 가진 Member 엔티티를 제거한다.

    for(Member member : members) {
     System.out.println(member.getName() + " " + member);
     for (Order order : member.getOrders()) {
     System.out.println(-> " + order.getName()+ " " + order);
    }
    /*
    회원1 Member@0x100
    -> 주문1 Order@0x200
    -> 주문2 Order@0x300
    회원2 Member@0x400
    -> 주문3 Order@0x500
    -> 주문4 Order@0x600
    */
    

페치 조인과 일반 조인의 차이

페치 조인의 특징과 한계

일대다 페치 조인 페이징 해결방법

정리