오봉이와 함께하는 개발 블로그
JPA 2 - 주문 조회 V6 : JPA에서 DTO로 직접 조회(플랫 데이터 최적화) 본문
728x90
주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화
조회 V6
OrderApiController에 추가
@GetMapping("/api/v6/orders")
public List<OrderFlatDto> ordersV6() {
return orderQueryRepository.findAllByDto_flat();
}
OrderQueryRepository에 추가
public List<OrderFlatDto> findAllByDto_flat() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderFlatDto" +
"(o.id, m.name, o.orderDate, d.address, o.status, i.name, oi.orderPrice, oi.count)" +
" from Order o" +
" join o.member m" +
" join o.delivery d" +
" join o.orderItems oi" +
" join oi.item i", OrderFlatDto.class)
.getResultList();
}
OrderFlatDto
@Data
public class OrderFlatDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private Address address;
private OrderStatus orderStatus;
// OrderItem
private String itemName;//상품 명
private int orderPrice; //주문 가격
private int count; //주문 수량
public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, Address address, OrderStatus orderStatus, String itemName, int orderPrice, int count) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.address = address;
this.orderStatus = orderStatus;
this.itemName = itemName;
this.orderPrice = orderPrice;
this.count = count;
}
}
Order와 OrderItem을 조인하고, OrderItem과 Item을 조인해서 한방 쿼리로 데이터를 가져온다.
데이터를 한 row에 담기 때문에 원하는 데이터와 DTO의 구조를 맞춰야 한다.
결과
위 코드를 실행하면 아래와 같은 결과가 나온다.
[
{
"orderId": 4,
"name": "userA",
"orderDate": "2022-09-06T21:57:57.690991",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderStatus": "ORDER",
"itemName": "JPA1 BOOK",
"orderPrice": 10000,
"count": 1
},
{
"orderId": 4,
"name": "userA",
"orderDate": "2022-09-06T21:57:57.690991",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderStatus": "ORDER",
"itemName": "JPA2 BOOK",
"orderPrice": 20000,
"count": 2
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2022-09-06T21:57:57.745537",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderStatus": "ORDER",
"itemName": "SPRING1 BOOK",
"orderPrice": 20000,
"count": 3
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2022-09-06T21:57:57.745537",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderStatus": "ORDER",
"itemName": "SPRING2 BOOK",
"orderPrice": 40000,
"count": 4
}
]
데이터가 중복으로 출력이 된다.
위 쿼리의 장점은 쿼리가 한 번만 나간다는 점이다.
단점은 Order
를 기준으로 페이징을 할 수 없고, OrderItem
기준으로 페이징이 된다는 점이다.
조회 V6.1
OrderQueryDto
구조로 API 스펙이 변경되었다.
그럼 어떻게 해야 할까?
내가 직접 중복을 걸러내면 된다.OrderFlatDto
에는 orderId
가 있기 때문에 중복인 경우 걸러내고 OrderFlatDto
를 루프 돌려서 OrderQueryDto
와 OrderItemQueryDto
에 맞게 데이터를 재구성하면 된다.
Controller
@GetMapping("/api/v6/orders")
public List<OrderQueryDto> ordersV6() {
List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();
return flats.stream()
.collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
mapping(o -> new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
)).entrySet().stream()
.map(e -> new OrderQueryDto(e.getKey().getOrderId(), e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(), e.getKey().getAddress(), e.getValue()))
.collect(toList());
}
flats을 통해 OrderQueryDto
, OrderItemQueryDto
를 하나로 묶어서 컬렉션으로 만든다.
만든 컬렉션을 통해 최종적으로 OrderQueryDto
의 생성자로 OrderQueryDto
를 만들어서 리스트로 반환한다.
OrderQueryDto에 생성자 추가
@EqualsAndHashCode(of = "orderId")
public class OrderQueryDto {
//..............
public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate,
OrderStatus orderStatus, Address address, List<OrderItemQueryDto> orderItems) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
this.orderItems = orderItems;
}
}
orderId
를 기준으로 .collect(groupingBy...)
의 기준을 잡아주기 위해 @EqualsAndHashCode(of = "orderId")
를 명시해주자.
결과
[
{
"orderId": 11,
"name": "userB",
"orderDate": "2022-09-06T23:07:12.354101",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 20000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 40000,
"count": 4
}
]
},
{
"orderId": 4,
"name": "userA",
"orderDate": "2022-09-06T23:07:12.296424",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderItems": [
{
"itemName": "JPA1 BOOK",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 20000,
"count": 2
}
]
}
]
11번 부터 출력되지만, sorting을 통해 해결할 수 있을 것이다.
정리
- 장점
- Query가 1번 나간다.
- 단점
- 쿼리는 한번이지만 조인으로 인해 DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 상황에 따라 V5 보다 느릴 수도 있다.
- 애플리케이션에서 추가 작업이 크다
- 데이터가 중복되기 때문에 Order를 기준으로 페이징이 불가능하다.
- OrderItem을 기준으로는 페이징을 할 수 있지만, OrderItem 기준으로 페이징 할 일이..?
인프런 김영한 지식공유자님 강의 - 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
728x90
'BE > JPA' 카테고리의 다른 글
JPA 2 - OSIV와 성능 최적화 (0) | 2022.09.07 |
---|---|
JPA 2 - API 개발 고급 정리 (0) | 2022.09.07 |
JPA 2 - 주문 조회V5 : JPA에서 DTO 직접 조회(컬렉션 조회 최적화) (0) | 2022.09.06 |
JPA 2 - 주문 조회V4 : JPA에서 DTO 직접 조회 (0) | 2022.09.06 |
JPA 2 - 주문 조회V3.1 : 엔티티를 DTO로 변환(페이징과 한계 돌파) (0) | 2022.09.06 |
Comments