JPA2 - API 개발 고급_컬렉션 조회 최적화

young·2023년 5월 23일
0

Spring Boot

목록 보기
16/19
post-thumbnail

🖇️컬렉션 조회 최적화

V1. 엔티티 직접 노출

  • 엔티티가 변하면 API 스펙이 변한다.
  • 트랜잭션 안에서 지연 로딩 필요
  • 양방향 연관관계 문제
Hibernate5Module 모듈 등록, LAZY=null 처리
양방향 관계 문제 발생 -> @JsonIgnore

V2. 엔티티를 조회해서 DTO로 변환(fetch join 사용X)

  • 트랜잭션 안에서 지연 로딩 필요
  • 지연 로딩으로 너무 많은 SQL 실행

< SQL 실행 수 >
1. order 1번
2. member , address N번(order 조회 수 만큼)
3. orderItem N번(order 조회 수 만큼)
4. item N번(orderItem 조회 수 만큼)

지연 로딩 은 영속성 컨텍스트에 있으면 영속성 컨텍스트에 있는 엔티티를 사용하고 없으면 SQL을 실행한다.
따라서 같은 영속성 컨텍스트에서 이미 로딩한 회원 엔티티를 추가로 조회하면 SQL을 실행하지 않는다.

V3. 엔티티를 조회해서 DTO로 변환(fetch join 사용O)

@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3() {
        List<Order> orders = orderRepository.findAllWithItem();
        List<OrderDto> result = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(toList());
        return result;
}
  • 페치조인으로 SQL은 1번만 실행
  • 페이징 시에는 N 부분을 포기해야함
    (대신에 batch fetch size 옵션 주면 N -> 1 쿼리로 변경 가능)

distinct 사용한 이유

1대다 조인이 있으므로 데이터베이스 row(열)증가 -> 같은 order 엔티티의 조회 수도 증가하게 된다.
JPA의 distinct는 SQL에 distinct를 추가하고, 같은 엔티티가 조회되면 애플리케이션에서 중복을 걸러준다.
=> order가 컬렉션 페치 조인때문에 중복 조회되는 것 방지

V3.1 엔티티를 조회해서 DTO로 변환 페이징 고려

 @GetMapping("/api/v3.1/orders")
public List<OrderDto> ordersV3_page(@RequestParam(value = "offset", defaultValue = "0")
                                        int offset, @RequestParam(value = "limit", defaultValue = "100") int limit) {
        List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);
        List<OrderDto> result = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(toList());
        return result;
}

[ 장점 ]
1. 쿼리 호출 수가 1+N -> 1+1로 최적화
2 .조인보다 DB데이터 전송량이 최적화됨
3. 페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만 DB 데이터 전송량 감소
4. 컬렉션 페치 조인은 페이징이 불가능하지만 이 방법은 페이징 가능

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

V4. JPA에서 DTO로 바로 조회, 컬렉션 N 조회

특징

  1. 코드 단순
  2. 페이징 가능
  3. 컬렉션은 별도로 조회
  4. 단건 조회(특정 주문 1건)에서 많이 사용하는 방식
  5. Quury : 루트 1번, 컬렉션 N번 실행
  • ToOne(N:1, 1:1)관계들을 먼저 조회하고, ToMany(1:N)관계는 각각 별도로 처리
    - ToOne 관계는 조인해도 데이터 row 수 증가 X => 조인으로 최적화 쉬우므로 한 번에 조회
    - ToMany(1:N)관계는 조인하면 row 수 증가 => 최적화어려우므로, findOrderItems() 같은 별도의 메서드로 조회

V5. JPA에서 DTO로 바로 조회, 컬렉션 1 조회 최적화 버전

특징

  1. 코드 복잡함
  2. 페이징 가능
  3. 데이터를 한꺼번에 처리할 때 많이 사용하는 방식
  4. Query : 루트 1번, 컬렉션 1번
  • ToOne 관계를 먼저 조회하고, 식별자 orderId로 ToMany 관계인 orderItem을 한꺼번에 조회
  • MAP을 사용해서 매칭 성능 향상

V6. JPA에서 DTO로 바로 조회, 플랫 데이터

특징

  1. V4와 V5와 다른 접근 방식
  2. (Order 기준) 페이징 불가능
  3. Query : 1번
    • 쿼리는 1번이지만, 조인으로 인해 DB -> 어플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 V5보다 느릴 수 있음
    • 어플리케이션에 추가 작업이 크다.
  4. 데이터가 많을 시에 중복 전송 증가로, V5 성능 차이 미비

🖇️ 권장 순서

  1. 엔티티 조회 방식으로 우선 접근
    1. 페치조인으로 쿼리 수를 최적화
    2. 컬렉션 최적화
      • 페이징 필요 ->hibernate.default_batch_fetch_size , @BatchSize
      • 페이징 필요 X -> 페치조인 사용
  2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용
  3. DTO 조회 방식으로 해결이 안되면 NativeSQL or 스프링 JdbcTemplate

엔티티 조회 방식 vs DTO 직접 조회하는 방식

엔티티 조회 = 페치 조인이나, hibernate.default_batch_fetch_size , @BatchSize 같이 코드를 거의 수정하지 않고, 옵션만 약간 변경해서, 다양한 성능 최적화를 시도할 수 있다.
DTO 조회 = 성능을 최적화 하거나 성능 최적화 방식을 변경할 때 많은 코드를 변경해야 한다.

profile
ฅʕ•̫͡•ʔฅ

0개의 댓글