시작이 반

[JPA] One To Many에서 join fetch ( 페이징 ) 본문

Programming/JPA

[JPA] One To Many에서 join fetch ( 페이징 )

G_Gi 2021. 6. 7. 21:53
SMALL

페치 조인(fetch join)은 엔티티를 한번에 조회하게 최적화를 해준다.

 

x To one 관계에서는 join fetch 를 해도되지만

 

one To many 에서는 Data 수가 many쪽에 맞춰 지면서 뻥튀기가 된다.

 

public class Order {

    @Id
    @GeneratedValue
    @Column(name = " order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "deliver_id")
    private Delivery delivery;

    private LocalDateTime orderDate; //주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; //주문상태 [ORDER, CANCEL]
}

일때

OrderItem을 join fetch 한다면

ex)

    public List<Order> findAllWithItem() {
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d" +
                        " join fetch o.orderItems oi" + //이 부분
                        " join fetch oi.item", Order.class
        ).getResultList();
    }

이런식으로 order에 대한 값이 4개로 늘어난다. ( 원래 2개였는데 join을 해서 )

orderId 4에 대한 값이 2개가 나왔다.

아래 내리면 orderId 11에 대한 값도 2개가 나와서 총 4개의 값이 나옴

 

이 문제를 해결하기 위해

    public List<Order> findAllWithItem() {
        return em.createQuery(
                "select distinct o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d" +
                        " join fetch o.orderItems oi" +
                        " join fetch oi.item", Order.class
        ).getResultList();
    }

distinct를 사용해준다.

 

물론 distinct는 DB에서는 완전 같은 행을 제거해준다.

 

여기서는 DB에서의 중복을 제거해 주는 역할도 하지만

JPA에서 중복된 객체(?) 즉 distinct o 를 해줌으로써 order의 중복을 제거해준다... ( 흠... 이렇게 이해하는게 맞나 )

정상적으로 2개의 값만 나왔다.

 

참고

- 페이징 불가능

one To many 컬렉션 페치 조인을 사용하면 페이징이 불가능하다.

DB에 있는 Data를 기준으로 페이징을 하기 때문에

1:다 관계에서는 페이징을 하면안된다...

 

- 컬렉션 페치 조인은 1개만 사용해야 한다.

 


페이징과 한계돌파

x To One관계까지만 fetch join을 한다.

 

컬렉션은 지연로딩으로 조회한다. (LAZY)

지연 로딩 성능 최적화를 위해

hibernate.default_batch_fetch_size(글로벌하게 적용할 때), @BatchSize(디테일하게 적용할 때)를 적용한다.

(1+n의 관계를 1+1 로 만들어준다.)

hibernate.default_batch_fetch_size를 쓰도록하자.

in query로 가져오는 것이다.

 

application.yml

결론

ToOne 관계는 페치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne 관계는 페치조인으로 쿼리 수를 줄이고 해결하고, 나머지는 hibernate.default_batch_fetch_size 로 최적화 하자.

 

 

batch_fetch_size:

더보기

default_batch_fetch_size 의 크기는 적당한 사이즈를 골라야 하는데, 100~1000 사이를 선택하는 것을 권장한다. 이 전략을 SQL IN 절을 사용하는데, 데이터베이스에 따라 IN 절 파라미터를 1000으로 제한하기도 한다. 1000으로 잡으면 한번에 1000개를 DB에서 애플리케이션에 불러오므로 DB 에 순간 부하가 증가할 수 있다. 하지만 애플리케이션은 100이든 1000이든 결국 전체 데이터를 로딩해야 하므로 메모리 사용량이 같다. 1000으로 설정하는 것이 성능상 가장 좋지만, 결국 DB든 애플리케이션이든 순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다

 


이외에 이제 DTO로 직접 조회하는 방법이 있다.

하지만 fetch, batch_fetch_size를 사용하면 거의 성능이 최적화된다.

 

 

참고:

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

LIST

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

[JPA]영속성  (0) 2021.11.25
[JPA]OSIV  (0) 2021.06.23
[JPA] 1 + n 조회, x To One에서 join fetch  (0) 2021.05.29
[JPA] xToMany - ManyToX  (0) 2021.05.29
[JPA] 변경감지와 병합(merge)  (1) 2021.05.27