Yebali

Spring을 이용한 API개발 - 조회 성능 최적화 본문

Spring

Spring을 이용한 API개발 - 조회 성능 최적화

예발이 2021. 10. 11. 19:42

Spring에서 조회 API를 개발할 때 조회 성능을 최적화하기 위한 방법들.

1. Entity -> DTO로 변환해서 조회

@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
 	
 	List<Order> orders = orderRepository.findAll();
 	List<SimpleOrderDto> result = orders.stream()
 				.map(o -> new SimpleOrderDto(o))
 				.collect(toList());
                
 	return result;
 }
 
@Data
static class SimpleOrderDto {
	private Long orderId;
	private String name;
	private LocalDateTime orderDate; 
	private OrderStatus orderStatus
	private Address address;
    
    	public SimpleOrderDto(Order order) {
        	orderId = order.getId();
        	name = order.getMember().getName();
        	orderDate = order.getOrderDate();
        	orderStatus = order.getStatus();
        	address = order.getDelivery().getAddress();
        }
}

Entity를 외부의 요청에 바로 반환 하는 것은 좋지 않다. 최소한 DTO로 변환해서 반환해야 한다.

단점 : DTO내부에 Collection이 있는 경우 쿼리가 총 1+N번 실행된다. -> N+1 문제 발생

2. Fetch join 최적화

Controller

@GetMapping("/api/v3/simple-orders")
public List<SimpleOrderDto> ordersV3() {
    List<Order> orders = orderRepository.findAllWithMemberDelivery();
    List<SimpleOrderDto> result = orders.stream()
            .map(o -> new SimpleOrderDto(o))
            .collect(toList());
    return result;
}

Repository

//Fetch Join을 사용해 Collection들을 함께 조회
public List<Order> findAllWithMemberDelivery() {
    return em.createQuery(
            "select o from Order o" +
            " join fetch o.member m" +
            " join fetch o.delivery d", Order.class)
	.getResultList();
}

Entity를 페치 조인(fetch join)을 사용해서 쿼리 1번에 모든 결과를 조회한다.
fetch join으로 order -> member, order -> delivery는 이미 조회된 상태이므로 지연 로딩이 발생하지 않는다.

3. JPA에서 DTO 바로 조회

Controller

@GetMapping("/api/v4/simple-orders")
public List<OrderSimpleQueryDto> ordersV4() {
      return orderSimpleQueryRepository.findOrderDtos();
}

Repository

public List<OrderSimpleQueryDto> findOrderDtos() {
        return em.createQuery(
                "select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name,
o.orderDate, o.status, d.address)" +
		" from Order o" +
    		" join o.member m" +
     		" join o.delivery d", OrderSimpleQueryDto.class)
	.getResultList();
}

일반적인 SQL을 사용할 때처럼 원하는 값들을 선택해서 조회한다.
장점 : Select절에서 원하는 데이터를 직접 선택하기 때문에 네트워크 용량 최적화
단점 : Repo의 재사용성이 떨어진다. API스펙에 맞춘 코드가 Repo에 들어가는 것은 좋지 않다.

정리

  1. 조회 API는 Entity를 DTO로 변환해서 반환한다.
  2. 필요 시 fetch join으로 성능을 최적화 한다. 
  3. 그래도 안되면 JPA에서 DTO를 직접 조회한다.
  4. 최후의 방법으로 JPA가 제공하는 네이티브SQL이나 스프링 JDBC Template을 사용하여 직접 SQL을 사용한다.