시작 전에 다시 복습을 해봅시다.
컬렉션에서 페치 조인을 하면 페이징 불가
컬렉션을 페치 조인하면 페이징이 불가능합니다.
- 컬렉션을 페치 조인하면 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가합니다.
- 일다대에서 일(1)을 기준으로 페이징을 하는 것이 목적입니다. 그런데 데이터는 다(N)를 기준으로 row 가 생성됩니다.
- Order를 기준으로 페이징 하고 싶은데, 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버립니다.
컬렉션을 페치 조인하면서 페이징하는 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도합니다. 최악의 경우 장애로 이어질 수 있습니다.
페이징하면서 컬렉션 엔티티를 함께 조회하는 방법
그러면 페이징하면서 컬렉션 엔티티를 함께 조회하려면 어떻게 해야할까요?
지금부터 코드도 단순하고, 성능 최적화도 보장하는 매우 강력한 방법을 소개하겠습니다. 대부분의 페이징 + 컬렉션 엔티티 조회 문제는 이 방법으로 해결할 수 있습니다.
- 먼저 ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인 합니다. ToOne 관계는 row수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않습니다.
- Order 입장에서 Member 와 Delivery 는 ToOne 관계입니다. ToOne 관계라면 계속 페치 조인을 해도 상관없습니다. (물론 데이터가 없는 경우는 페치 레프트 조인을 해야겠죠.)
- 컬렉션은 지연 로딩으로 조회합니다.
- Order 입장에서 OrderItem 은 ToOne 관계가 아닙니다. 그래서 페치 조인을 하면 성능 최적화가 불가능해지죠.
- Item 도 Order 와 OrderItem 이 일대다로 조인 후에 조인을 해야하니 OrderItem 입장에서 Item 은 ToOne 관계지만 페치 조인을 하면 안됩니다.
- 즉, Member 와 Delivery 는 한방 쿼리로 작성했지만 OrderItem 와 Item 은 지연 로딩으로 조회합니다.
- 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size , @BatchSize 를 적용합니다.
- hibernate.default_batch_fetch_size
- @BatchSize
- 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회합니다.