join vs fetch join

  • join : jpql에서 join을 사용해도 영속화 되지 않는다. 즉, 조회되는 주체만 영속화 된다. 따라서 join이 될 entity는 필요없고 검색조건에 필요한 경우에 사용한다.
  • fetch join : 조회하는 주체와 fetch join을 통해 조인할 entity 모두 영속화 되어서 반환된다.

 

fetch join 주의 사항

  • ~ToOne을 몇 개가 되든 fetch join 시킬 때는 문제가 없지만, ~ToMany의 경우 데이터가 카테시안 곱만큼 늘어나서 조회가 된다. 내부적으로 fetch join은 inner join을 사용하기 때문이다. 즉, 영속화 되기 때문에 One table에 Many table이 모두 속하게 되지만, 아래와 같이 같은 One table을 Many table과 연관된 만큼 반환하게 된다.

사진1
사진2

  • 이를 해결하려면 distinct를 사용하면 되는데, SQL에서의 distinct의 기능과 추가로 애플리케이션에서 중복된 엔티티를 제거하는 역할을 한다. 이는 JPA는 DB로부터 반환된 결과에서 중복된 객체(=동일한 메모리 주소)를 가지는 데이터를 삭제함으로써 이뤄진다.

  • 별칭을 줄 수 없다. 하이버네이트에서는 사용 가능 하지만 가급적으로는 사용을 지양하자.
    • where절에서 사용하는 경우를 대비하기 위함이다.
      • fetch join은 연관된 entity를 모두 조회한다는 정의를 가지고 있다.
      • 만약 where절에 fetch join 대상을 사용한다면 정의에 위배된다.
    • 재귀적인 조인만을 위할 때는 별칭을 주어도 fetch join의 정의를 위배하지 않으므로 상관없다.
  • 둘 이상의 컬렉션은 fetch join 할 수 없다. 하나에 엔티티에 @OneToMany가 있고, 이에 대해 fetch join을 사용하면 MultipleBagFetchException이 발생한다. ~ToOne은 여러 개의 fetch join을 할 수 있다.
  • OneToMany, ManyToMany 관계에서는 페이징이 불가능하다. 사진1, 2와 같이 데이터의 수가 변하기 때문이다. 그래서 jpa에서는 paging을 사용하면서 컬렉션 관계를 fetch join하는 것 자체를 막아두었다. 만약 이를 어기고 사용할 시에 경고 로그가 뜨고, 쿼리에서 페이징 처리를 안 하고(limit, offset 쿼리문에 없음) 메모리에서 페이징을 적용하므로 메모리에서 뻑날 수 있다.
    • limit, offset 쿼리가 없어지는 이유는, 3개에 데이터가 나와야 하는데 중복된 데이터에 의해 1개만 반환될 수 있기 때문이다. 즉, limit 3인데 1 데이터가 3개 있으면 1만 나오는 문제 때문에 없애준다. 
    • 만약 distinct 안하고 페이징을 사용하면 중복된 게 나오고, distinct 하고 페이징을 하면 중복된 것이 안 나온다.
    • 일반 조인에서는 lazy하게 가져오므로 중복된 데이터가 없다. 따라서 페이징이 가능하다. limit, offset 쿼리가 있음.

@OneToMany 2개 이상일 때 fetch join 문제 해결법

  • N+1 문제란 결국 부모 엔티티의 key를 통해 자식 엔티티들을 찾고자 할 때 발생하는 것으로, 부모의 키들을 in으로 묶어서 자식 엔티티들을 불러오면 추가로 발생하는 쿼리를 획기적으로 줄일 수 있다.  

  • 이 때 사용하는 것을 하이버네이트의 Hibernate default_batch_fetch_size옵션이다. application.properties에 들어가 spring.jpa.properties.hibernate.default_batch_fetch_size=1000와 같이 설정하면 된다. 보통 옵션값은 1000을 넘으면 안된다. 너무 많은 in 절 파라미터로 인해 문제가 발생할 수 있다. 이 때는 쿼리가 n/1000 발생한다.
  • fetch join보다 성능이 뒤떨어지기 때문에 데이터가 많은 자식 테이블에 대해서는 fetch join 하고, 나머지 @OneToMany테이블에는 위의 옵션을 사용하자.
  • 지연로딩인 상태에서 데이터를 가져올 때 batch가 되는 것이다.
  • batch size가 10일 때 자식 엔티티가 11개 인 것은 상관없다. 부모 엔티티의 개수가 10개인 것을 말한다.

컬렉션을 fetch join할 때 페이징 문제 해결법

  • @OneToMany 2개 이상일 때 fetch join 문제 해결법과 마찬가지로 Hibernate default_batch_fetch_size옵션을 통해 페이징을 해야한다.
  • 즉, 먼저 주 엔티티를 먼저 조회한다음, 이 주 엔티티가 가지고 있는 batch를 통해 조회해준다.

cf)https://velog.io/@jadenkim5179/Spring-defaultbatchfetchsize%EC%9D%98-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC Hibernate default_batch_fetch_size의 작동원리

+ Recent posts