Java/JPA

[API 개발 고급] 지연 로딩과 조회 성능 최적화(V4) 4/4

Lea Hwang 2022. 5. 26. 21:04

간단한 주문 조회 V4: JPA에서 DTO로 바로 조회

  • 쿼리 1번 호출
  • select 절에서 원하는 데이터만 선택해서 조회

 

V3까지는 엔티티 조회 후 DTO로 변환하는 작업을 했습니다. 

V4에서는 JPA에서 DTO로 바로 조회하면서 조금 더 성능 최적화를 해보겠습니다. 

 

 

의존관계
Repository에서 Controller 쪽으로 의존관계가 생기면 망합니다.

의존관계는 한 방향으로 해야합니다.
Controller → Service → Repository로 가야 합니다. 

 

 

OrderSimpleApiController

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

 

OrderSimpleQueryDto

package jpabook.jpashop.repository;

@Data
public class OrderSimpleQueryDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address; //배송지 주소

        public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
            this.orderId = orderId;
            this.name = name;
            this.orderDate = orderDate;
            this.orderStatus = orderStatus;
            this.address = address;
        }

}

 

OrderRepository

public List<OrderSimpleQueryDto> findOrderDtos() {
 	return em.createQuery(
                "select o from Order o" +
                " join o.member m" +
                " join o.delivery d", OrderSimpleQueryDto.class)
        .getResultList();
 }

이렇게 코드를 짰을 경우 OrderSimpleQueryDto에 매핑될 수가 없습니다.

 

엔티티나 값 타입만 반환할 수 있고 나머지는 안됩니다.
하기 위해서는 new 명령어를 사용해야 합니다.

 

수정

OrderRepository

public List<OrderSimpleQueryDto> findOrderDtos() {
    return em.createQuery(
                    "select new jpabook.jpashop.repository.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();
}

 

 

Postman 확인

 

 

V4 쿼리

/* select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) from Order o join o.member m join o.delivery d */ 
select 
    order0_.order_id as col_0_0_, 
    member1_.name as col_1_0_, 
    order0_.order_date as col_2_0_, 
    order0_.status as col_3_0_, 
    delivery2_.city as col_4_0_, 
    delivery2_.street as col_4_1_, 
    delivery2_.zipcode as col_4_2_ 
from 
    orders order0_ 
inner join 
    member member1_ 
        on order0_.member_id=member1_.member_id 
inner join 
    delivery delivery2_ 
        on order0_.delivery_id=delivery2_.delivery_id

 

V3코드와 비교해봅시다.

/* select o from Order o join fetch o.member m join fetch o.delivery d */ 
select 
    order0_.order_id as order_id1_6_0_, 
    member1_.member_id as member_i1_4_1_, 
    delivery2_.delivery_id as delivery1_2_2_, 
    order0_.delivery_id as delivery4_6_0_, 
    order0_.member_id as member_i5_6_0_, 
    order0_.order_date as order_da2_6_0_, 
    order0_.status as status3_6_0_, 
    member1_.city as city2_4_1_, 
    member1_.street as street3_4_1_, 
    member1_.zipcode as zipcode4_4_1_, 
    member1_.name as name5_4_1_, 
    delivery2_.city as city2_2_2_, 
    delivery2_.street as street3_2_2_, 
    delivery2_.zipcode as zipcode4_2_2_, 
    delivery2_.status as status5_2_2_ 
from 
    orders order0_ 
inner join 
    member member1_ 
        on order0_.member_id=member1_.member_id 
inner join 
    delivery delivery2_ 
        on order0_.delivery_id=delivery2_.delivery_id

 

V3와 V4의 쿼리가 from이나 inner join부분을 같으나 

V4의 경우 select에서 내가 원하는 것만 셀렉트 했습니다.

 

 

 

그럼 V4가 더 좋은 걸까요?

장단 있습니다.

 

V3는 

재사용성 좋음

코드 가독성 좋음

  많은 API에서 사용 가능

 

 

V4는

재사용성이 없다.

조오오오금 더 성능 최적화

코드상 지저분해 보인다

 

 

 

하지만 V3와 V4의 성능 차이가 많이 날까요?

대부분의 성능은 join에서 잡아먹는 것이기에 사실 select절에서 필드 몇 개 더 넣는다고 성능을 많이 잡아먹진 않습니다.

 

 

 

 

V4를 단점 상쇄 방법 :

Repository는 순수한 Entity를 조회/검색/성능 최적화 위한 페치 조인하는 데 사용해야 합니다.

하지만 OrderRepository에 만든 findOrderDtos()는 DTO를 조회하는 것입니다.
실무에서는 Repository 하위에 성능 최적화 쿼리용 패키지를 새로 생성해서 넣는 방법을 사용하기도 합니다.

 

 

정리

엔티티를 DTO로 변환하거나, DTO로 바로 조회하는 두 가지 방법은 각각 장단점이 있기에 상황에 따라서 더 나은 방법을 선택하면 됩니다.

엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해집니다.

 

 

 

쿼리 방식 선택 권장 순서

1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.(V2)

2. 필요하면 페치 조인으로 성능을 최적화한다. 대부분의 성능 이슈가 해결된다.(95% 이상)(V3)

3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.(V4)

4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.

 

 

 

 

여기까지 @XToOne 관계였습니다.

다음 포스팅에서는 일대다 관계에서의 성능 최적화에 대해 알아보겠습니다.