오봉이와 함께하는 개발 블로그
JPA 2 - 주문 조회V4 : JPA에서 DTO 직접 조회 본문
728x90
주문 조회 V4: JPA에서 DTO 직접 조회
OrderApiController에 추가
@GetMapping("/api/v4/orders")
public List<OrderQueryDto> ordersV4() {
return orderQueryRepository.findOrderQueryDtos();
}
OrderQueryRepository
package jpabook.jpashop.repository.order.query;
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
private final EntityManager em;
/**
* 컬렉션은 별도로 조회
* Query: 루트 1번, 컬렉션 N 번
* 단건 조회에서 많이 사용하는 방식
*/
public List<OrderQueryDto> findOrderQueryDtos() {
//루트 조회(toOne 코드를 모두 한번에 조회)
List<OrderQueryDto> result = findOrders(); // query 1번 -> N개
//루프를 돌면서 컬렉션 추가(추가 쿼리 실행)
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId()); // query N번 실행
o.setOrderItems(orderItems);
});
return result;
}
/**
* 1:N 관계(컬렉션)를 제외한 나머지를 한번에 조회
*/
private List<OrderQueryDto> findOrders() {
// new 오퍼레이션을 사용하면 문제는 컬렉션을 파라미터로 바로 넣지 못 한다는 점이다.
// OrderItem은 Order와 일대다 관계이기 때문에 데이터가 뻥튀기 되기 때문에 아래와 같이 쿼리를 날리고 끝을 낸다.
return em.createQuery(
"select new " +
"jpabook.jpashop.repository.order.query.OrderQueryDto" +
"(o.id, m.name, o.orderDate, o.status, d.address) " +
"from Order o " +
"join o.member m " +
"join o.delivery d", OrderQueryDto.class)
.getResultList();
}
/**
* 1:N 관계인 orderItems 조회
*/
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto" +
"(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id =: orderId", OrderItemQueryDto.class)
.setParameter("orderId", orderId)
.getResultList();
}
}
리포지토리는 엔티티를 조회하는 용도로 사용하고, 쿼리쪽은 화면이나 API에 맞춤 설계를 위한 코드들을 위해 분리한다.
OrderQueryDto
package jpabook.jpashop.repository.order.query;
@Data
public class OrderQueryDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemQueryDto> orderItems;
public OrderQueryDto(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;
}
}
OrderItemQueryDto
package jpabook.jpashop.repository.order.query;
@Data
public class OrderItemQueryDto {
@JsonIgnore
private Long orderId;
private String itemName;
private int orderPrice;
private int count;
public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
this.orderId = orderId;
this.itemName = itemName;
this.orderPrice = orderPrice;
this.count = count;
}
}
정리
- Query
- 루트(findOrders()) 1번 -> N개 조회
- 컬렉션 N 번 실행
- N + 1 문제 발생
- XToOne(N:1, 1:1) 관계들을 먼저 조회하고, XToMany(1:N) 관계는 각각 별도로 처리한다.
- 이런 방식을 선택한 이유는 다음과 같다.
- XToOne(N:1, 1:1) 관계는 조인해도 데이터 row 수가 증가하지 않는다.
- XToMany(1:N) 관계는 조인하면 row 수가 증가한다.
- 이런 방식을 선택한 이유는 다음과 같다.
- row 수가 증가하지 않는 XToOne 관계는 조인으로 최적화 하기 쉬우므로 한번에 조회하고, XToMany 관계는 최적화 하기 어려우므로 findOrderItems() 같은 별도의 메서드로 조회한다.
인프런 김영한 지식공유자님 강의 - 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
728x90
'BE > JPA' 카테고리의 다른 글
JPA 2 - 주문 조회 V6 : JPA에서 DTO로 직접 조회(플랫 데이터 최적화) (0) | 2022.09.06 |
---|---|
JPA 2 - 주문 조회V5 : JPA에서 DTO 직접 조회(컬렉션 조회 최적화) (0) | 2022.09.06 |
JPA 2 - 주문 조회V3.1 : 엔티티를 DTO로 변환(페이징과 한계 돌파) (0) | 2022.09.06 |
JPA 2 - 주문 조회V3 : 엔티티를 DTO로 변환(페치 조인 최적화) (0) | 2022.09.05 |
JPA 2 - 주문 조회V2 : 엔티티를 DTO로 변환 (0) | 2022.09.05 |
Comments