//주문 단순 조회 v1@GetMapping("/api/v1/simple-orders")publicList<Order>ordersV1(){List<Order>all=orderRepository.findAll(newOrderSearch());for(Orderorder:all){order.getMember().getName();//Lazy 강제 초기화 (Member 엔티티)order.getDelivery().getAddress();//Lazy 강제 초기화 (Delivery 엔티티)}returnall;}
publicList<Order>findAll(){returnem.createQuery("select o from Order o",Order.class).getResultList();}
Hibernate5Module
JPA 사용 시 일반적으로 fetch 전략을 LAZY로 잡기 때문에 실제 엔티티 객체 대신에 프록시 객체를 갖고 있다.
jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 모르기 때문에 예외가 발생한다.
이를 해결하기 위해 Hibernate5Module 또는 Hibernate5JakartaModule를 스프링 빈으로 등록해준다.
엔티티를 직접 반환할 때 해당 엔티티에 양방향 연관관계가 존재한다면 양측 엔티티가 서로 호출하면서 무한 루프가 발생한다.
@JsonIgnore를 추가해서 순환 참조를 막는다.
@JsonIgnore는 주로 @ManyToOne 어노테이션이 있는 필드에 추가하면 된다.
v2 - 엔티티를 DTO로 변환
엔티티를 DTO로 변환 후 반환하는 방법
조회한 엔티티를 DTO의 생성자를 통해 DTO로 변환한다.
장점
해당 API를 위한 어느정도 고정된 스펙의 DTO를 반환하기 때문에 API의 스펙이 변경될 일이 적다.
단점
지연 로딩이기 때문에 N + 1의 문제가 존재한다.
발생하는 문제의 유형을 부르는 이름이 N + 1인거지 실제로는 더 많은 쿼리가 발생할 수도 있다.
만약에 조회한 엔티티 내부에 프록시로 존재하는 필드가 M개 있다면 실행되는 쿼리의 개수는 1 + N * M이 될 수도 있다.
팁
map(o -> new SimpleOrderDto(o))는 람다 레퍼런스 방식으로 map(SimpleOrderDto::new)처럼 표현할 수 있다.
//주문 단순 조회 v2@GetMapping("/api/v2/simple-orders")publicList<SimpleOrderDto>ordersV2(){List<Order>orders=orderRepository.findAll();List<SimpleOrderDto>result=orders.stream().map(o->newSimpleOrderDto(o)).collect(toList());returnresult;}
publicList<Order>findAll(){returnem.createQuery("select o from Order o",Order.class).getResultList();}
@DatapublicclassSimpleOrderDto{privateLongorderId;privateStringname;privateLocalDateTimeorderDate;privateOrderStatusorderStatus;privateAddressaddress;publicSimpleOrderDto(Orderorder){orderId=order.getId();name=order.getMember().getName();//Lazy 강제 초기화 (Member 엔티티)orderDate=order.getOrderDate();orderStatus=order.getStatus();address=order.getDelivery().getAddress();//Lazy 강제 초기화 (Delivery 엔티티)}}
v3 - 페치 조인 최적화
페치 조인(fetch join)으로 데이터를 한꺼번에 가져온다.
엔티티로 조회했기 때문에 DB와의 작업이 수월하다. (ex : 변경 감지)
유연도가 높은 방식이다. (v4에 비해서 비교적 높은 편)
//주문 단순 조회 v3@GetMapping("/api/v3/simple-orders")publicList<SimpleOrderDto>ordersV3(){List<Order>orders=orderRepository.findAllWithMemberDelivery();List<SimpleOrderDto>result=orders.stream().map(o->newSimpleOrderDto(o)).collect(toList());returnresult;}
publicList<Order>findAllWithMemberDelivery(){returnem.createQuery("select o from Order o "+"join fetch o.member m "+"join fetch o.delivery d",Order.class).getResultList();}
v4 - JPA에서 DTO로 바로 조회
DB에서 조회한 데이터를 엔티티가 아닌 DTO로 바로 받는 방식
DTO로 조회했기 때문에 DB와의 작업이 수월하지 못 하다. (ex : 변경 감지)
유연도가 낮은 방식이다. (v3에 비해서 비교적 낮은 편)
화면에 최적화되 있는 방식
대신에 특정 DTO에 의존도가 높다.
JPQL 작성 방법이 좀 번거롭다. (패키지명 직접 명시)
//주문 단순 조회 v4@GetMapping("/api/v4/simple-orders")publicList<OrderSimpleQueryDto>ordersV4(){returnorderSimpleQueryRepository.findOrderDtos();}
//패키지명을 직접 명시해줘야 한다.//사용하는 IDE에서 관련 기능을 사용할 수 있다면 그나마 사용성이 증가한다.publicList<OrderSimpleQueryDto>findOrderDtos(){returnem.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();}