[스프링과 JPA활용 2편] 요약 정리3

sonnng·2023년 11월 3일
0

Spring

목록 보기
25/41

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

api controller

	@GetMapping("/api/v3/simple-orders")
    public List<SimpleOrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithMemberDeliver();
        List<SimpleOrderDto> result = orders.stream().map(SimpleOrderDto::new).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(); //LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); //LAZY 초기화
        }
    }

repository

	public List<Order> findAllWithMemberDeliver(){
        return em.createQuery(
                        "select o from Order o" +
                                " join fetch o.member m" +
                                " join fetch o.delivery d", Order.class)
                .getResultList();
        //이렇게 하면 order의 MEMBER, DELIVERY가 LAZY로 페치전략으로 되어있어도 한번에 실제 값으로 객체가 조회된다.
    }

엔티티를 페치조인하여 쿼리 1번으로 조회하게 되고 이미 이렇게 select로 모두 조회하므로 지연로딩으로 인한 추가적인 쿼리문이 발생하지 않게 된다.

😶‍🌫️ 주의해야할 점

사진에서 보다시피 모든 필드를 조회해오기 때문에 아주 약간의 성능문제가 발생할 수 있다는 점이다.

😲 알고있으면 좋은 점

리포지토리의 역할은 엔티티를 조회해오고, 그 엔티티를 조회하기 위해 테이블의 그래프 탐색이라는 것을 수행한다. 그렇기 때문에 리포지토리 내 메서드의 리턴값의 제네릭이 엔티티 Order인 것은 당연히 이렇게 작성해야 한다. 만약 DTO를 반환하도록 메서드를 작성했다면, 그건 API 스펙에 맞게만 작성한 것이므로 API 스펙이 바뀌면 리포지토리를 계속 수정해야한다.. 그렇기에 강사는 엔티티 제네릭을 리턴하는 것을 권장한다.



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

api controller

 	@GetMapping("/api/v4/simple-orders")
    public List<OrderSimpleQueryDto> orderV4(){
        return orderSimpleQueryRepository.findOrderDtos(); //리포지토리는 엔티티를 조회하는데 써야한다. 그래프 탐색 용도여야 하지만, 이 api는
        //그런 용도에 알맞지 않다.
    }

DTO

@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 status, Address address) {
        orderId = orderId;
        name = name; //LAZY 초기화
        orderDate = orderDate;
        orderStatus = status;
        address = address; //LAZY 초기화
    }
}

query 전용 리포지토리 생성

@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {

    private final EntityManager em;
    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();
    }
}

🌟레포지토리에서 JPQL을 활용한 결과를 dto로 즉시 변환할 때의 이점

  1. 애플리케이션 네트웍 용량 최적화(생각보다 미비)
    SELECT절에서 원하는 데이터를 직접 선택하므로 최적화를 이룰 수 있다.

🌟레포지토리에서 JPQL을 활용한 결과를 dto로 즉시 변환할 때의 단점

  1. 리포지토리 재사용성 떨어짐
    API를 만들 때마다 새로 메서드를 작성해야하며, 한번 만든 리포지토리를 하나의 역할밖에 못하게 된다.


➡️엔티티(fetch join)를 dto로 변환하거나 dto(inner join)로 바로 조회하는 두 방법은 각각 장단점이 있다.
상황에 따라 더 나은 방법을 선택해서 사용하면 된다. 다만, 엔티티로 조회하여 DTO로 변환하는 건 더 재사용성이 높고 개발도 단순해진다.

  • 쿼리 전용 레포지토리 디렉토리로 따로 생성해서 관리하면 좋다.
    일반적으로 레포지토리는 엔티티를 반환하도록 하고 이를 DTO로 변환하는 방법이 대다수고, 그 역할을 하기 때문이다. 만약 dto로 바로 변환하는 조회방법을 선택한다면 쿼리전용 레포지토리 디렉토리를 생성해 관리하도록 한다. 특히 이 경우는 dto 클래스도 같은 디렉토리에 놓도록 한다.


쿼리방식선택권장순서

  1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
  2. 필요하면 페치조인으로 성능을 최적화 한다.
    대부분의 성능 이슈가해결된다.
  3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
  4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.

0개의 댓글