컬렉션 조회 최적화

강한친구·2022년 7월 25일
0

JPA

목록 보기
16/27

JPA에서 DTO로 직접조회

public class OrderQueryDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate; //주문시간
    private OrderStatus orderStatus;
    private Address address;
    private List<OrderItemQueryDto> orderItems;
    }

위와 같은 DTO를 JPA에서 직접 조회해보도록 하겠다.

Order안에 있는 OrderItems 역시 Entity이기 때문에, orderItems를 담을 DTO를 하나 더 만들어야한다.

@Data
public class OrderItemQueryDto {

    @JsonIgnore
    private Long orderId; //주문번호
    private String itemName;//상품 명
    private int orderPrice; //주문 가격
    private int count;      //주문 수량

    public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}

쿼리보내기

그 전에, 이 둘의 관계를 살펴보면 Order와 orderItems는 1:N 관계라는것을 알 수 있다. 따라서, Order를 조회하고 이 결과에 OrderItems를 따로 조회해서 값을 넣어주어야한다.

public List<OrderQueryDto> findOrderQueryDtos() {
        //루트 조회(toOne 코드를 모두 한번에 조회)
        List<OrderQueryDto> result = findOrders();

        //루프를 돌면서 컬렉션 추가(추가 쿼리 실행)
        result.forEach(o -> {
            List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
            o.setOrderItems(orderItems);
        });
        return result;
    }

이 방식은 쿼리가 n+1번 나가게된다.


최적화하기

위의 방식대로 하면, Order를 찾고 각 order당 orderItem을 찾기 위한 쿼리를 한번씩 더 날리는것을 볼 수 있다. 이를 방지하기 위한 방법을 적용해보겠다.

optimization

    public List<OrderQueryDto> findAllByDto_optimization() {

        List<OrderQueryDto> result = findOrders();

        Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result));

        result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));

        return result;
    }
  1. Order를 전부 찾는다.
  2. OrderId에 맞는 OrderItem을 전부 찾아서 Map형태로 저장한다.
  3. 루프를 돌면서 컬렉션에 OrderItem을 추가한다.

다음과 같은 과정을 거치면 1회의 조회로 OrderItem을 전부 가지고 올 수 있다.

Flat Data

이제 아예 한번에 조회를 하는 쿼리를 만들어볼 수 있다.

@Data
public class OrderFlatDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate; //주문시간
    private Address address;
    private OrderStatus orderStatus;

    private String itemName;//상품 명
    private int orderPrice; //주문 가격
    private int count;      //주문 수량

    public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }

}

다음과같은 DTO에 담을것이다.

orderItem을 담기위한 변수들이 지정된것을 볼 수 있다.

정리

정리
  엔티티 조회
  엔티티를 조회해서 그대로 반환: V1
  엔티티 조회 후 DTO로 변환: V2
  페치 조인으로 쿼리 수 최적화: V3
  컬렉션 페이징과 한계 돌파: V3.1
    컬렉션은 페치 조인시 페이징이 불가능
    ToOne 관계는 페치 조인으로 쿼리 수 최적화
    컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size , @BatchSize 로 최적화
DTO 직접 조회
  JPA에서 DTO를 직접 조회: V4
  컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화: V5
  플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변환: V6
권장 순서
	1. 엔티티 조회 방식으로 우선 접근
      1. 페치조인으로 쿼리 수를 최적화
      2. 컬렉션 최적화
        1. 페이징 필요 hibernate.default_batch_fetch_size , @BatchSize 로 최적화
        2. 페이징 필요X 페치 조인 사용
    2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용
    3. DTO 조회 방식으로 해결이 안되면 NativeSQL or 스프링 JdbcTemplate

0개의 댓글