[JPA 활용 2] API 개발 고급 - 컬렉션 조회 최적화 ❶

홍정완·2022년 6월 8일
0

JPA

목록 보기
10/38
post-thumbnail

이번에는 컬렉션인 일대다 관계(OneToMany)를 조회하고, 최적화하는 방법을 알아보자.


주문내역에서 추가로 주문한 상품 정보를 추가로 조회하자.

Order 기준으로 컬렉션인 OrderItem과 Item이 필요하다.




주문 조회 V2 : 엔티티를 DTO로 변환


@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2() {
		 
	List<Order> orders = orderRepository.findAll();
    
 	List<OrderDto> result = orders.stream()
 			.map(o -> new OrderDto(o))
 			.collect(toList());
 	
    return result;

}



// OrderApiController에 추가

@Data
static class OrderDto {
 
 	private Long orderId;
 	private String name;
 	private LocalDateTime orderDate; //주문시간
 	private OrderStatus orderStatus;
 	private Address address;
 	private List<OrderItemDto> orderItems;
 
 
 	public OrderDto(Order order) {
    
 		orderId = order.getId();
 		name = order.getMember().getName();
 		orderDate = order.getOrderDate();
 		orderStatus = order.getStatus();
		address = order.getDelivery().getAddress();
 		orderItems = order.getOrderItems().stream()
 				.map(orderItem -> new OrderItemDto(orderItem))
 				.collect(toList());
                
 	}

}


@Data
static class OrderItemDto {
 
 	private String itemName;//상품 명
 	private int orderPrice; //주문 가격
 	private int count; //주문 수량
 
 	
    public OrderItemDto(OrderItem orderItem) {
 	
    	itemName = orderItem.getItem().getName();
 		orderPrice = orderItem.getOrderPrice();
 		count = orderItem.getCount();
        
 	}
    
}



  • 단순히 DTO로 변환하기만 하는 위의 방법은 지연 로딩으로 많은 SQL이 실행된다.

  • SQL 실행 수

    • order 1번

    • member, address     N번(order 조회 수 만큼)

    • orderItem     N번(order 조회 수 만큼)

    • item     N번(orderItem 조회 수 만큼)



지연 로딩은 영속성 컨텍스트에 있으면 영속성 컨텍스트에 있는 엔티티를 사용하고, 없으면 SQL을 실행한다.

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




주문 조회 V3 : 엔티티를 DTO로 변환 - 페치 조인 최적화


// OrderApiController에 추가

@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;

}



// OrderRepository에 추가

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 i", Order.class)
 			.getResultList();

}



  • 페치 조인으로 SQL이 1번만 실행된다.

  • distinct를 사용한 이유는 1대다 조인이 있으므로 데이터베이스 row가 증가한다. 그 결과 같은 order 엔티티의 조회 수도 증가하게 된다. JPA의 distinct는 SQL에 distinct를 추가하고, 더해서 같은 엔티티가 조회되면, 애플리케이션에서 중복을 걸러준다. 이 예에서 order가 컬렉션 페치 조인 때문에 중복 조회 되는것을 막아준다.

  • 페이징 불가능



컬렉션 페치 조인을 사용하면 페이징이 불가능하다.
하이버네이트는 경고 로그를 남기면서 모든데이터를 DB에서 읽어오고,
메모리에서 페이징 해버린다(매우 위험).

컬렉션 페치 조인은 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치 조인을 사용하면 안된다.

profile
습관이 전부다.

0개의 댓글