-> 컬렉션이 있을 때 하는 방법 다루기
-> OrderQueryDto 를 OrderQueryRepository에 구현함
1) repository/order/query 안
package jpabook.jpashop.repository.order.simplequery;
import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.OrderStatus;
import lombok.Data;
import java.time.LocalDateTime;
@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;
}
}
package jpabook.jpashop.repository.order.query;
import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.OrderStatus;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@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;
}
}
package jpabook.jpashop.repository.order.query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
private final EntityManager em;
//이렇게 따로만들어준 이유가
//만약 api안에있는 orderDto쓰면 리포지토리가 컨트롤러에 의존하는 순환관계발생해
//본인이 만드는 애여서 그냥 같은 패키지에 넣음
public List<OrderQueryDto> findOrderQueryDtos(){
List<OrderQueryDto> result = findOrders();
//이제 루프를 돌려서 orderitems에서 받아와
//컬렉션부분은 루프 돌면서 직접 채워주기
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
//1대다 해결하려고 이렇게함
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();
}
private List<OrderQueryDto> findOrders() {
//new 오퍼레이션쓰면 플랫하게 데이터를 한줄밖에 못넣어서 리스트를 매개변수로 못넣음
//orderitmes는 1대다여서 데이터 뻥튀기돼서 한번에 못넣어서 다음에 넣어줘
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();
}
}
2) v4함수
@GetMapping("/api/v4/orders")
public List<OrderQueryDto> ordersV4(){
return orderQueryRepository.findOrderQueryDtos();
}
컬렉션이랑 디티오랑 다르게! 아이템때문에 따로 가져오려고 orderItemQueryDto 만들어준것임
[
{
"orderId": 4,
"name": "userA",
"orderDate": "2022-11-08T17:33:05.845134",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderItems": [
{
"orderId": 4,
"itemName": "Jpa1 Book",
"orderPrice": 10000,
"count": 1
},
{
"orderId": 4,
"itemName": "Jpa2 Book",
"orderPrice": 20000,
"count": 2
}
]
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2022-11-08T17:33:05.87042",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderItems": [
{
"orderId": 11,
"itemName": "Spring1 Book",
"orderPrice": 10000,
"count": 1
},
{
"orderId": 11,
"itemName": "Spring2 Book",
"orderPrice": 20000,
"count": 2
}
]
}
]
-> 잘 나온다!
-> 단점이 쿼리가 오더한번, 오더아이템 2개해서 총 3번
-> 즉, n+1 번 나가! 루프때문에 그런거임. 컬렉션은 루프로 직접 넣어줘서!
public List<OrderQueryDto> findAllByDto_optimization() {
//오더가져오는것까지는 동일해서 findOrders()는 그대로 사용함
List<OrderQueryDto> result = findOrders();
//orderIds는 orderQueryDto에서 나온걸 stream으로 아이디만 다 뽑는것
//주문가져온걸 스트림으로 아이디들을 리스트로 받아
List<Long> orderIds = result.stream()
.map(o -> o.getOrderId())
.collect(Collectors.toList());
//V4의 단점은 루프를 도는건데, 이번엔 한번에 가져옴
//아이디를 하나씩가져오는게아니라 in절로 한번에 가져오기
//오더아이디 리스트를 파라미터 인자로 넣어서 오더아이템 관련된거 뽑혀져 나올것임
//쿼리한번으로 오더아이템 해결!
List<OrderItemQueryDto> orderItems = 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 in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
//최적화하기
//쿼리한번 날리고 메모리에서 매칭해서 값 세팅해주기
//이렇게하면 쿼리가 총 두번나가 findOrders랑 위의 쿼리문
java.util.Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
result.forEach(o->o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
@GetMapping("/api/v5/orders")
public List<OrderQueryDto> ordersV5(){
return orderQueryRepository.findAllByDto_optimization();
}
-> 얘도 잘나가!
-> 쿼리가 두번만 나가! 맨처음 findOrders()랑 그 뒤에 아이디로 애들 다 데려오기
-> in query를 써서 id랑 관련된것들 다 가져오고 메모리에서 매치시킨것
-> 리팩토링해서 더 가시성좋게 함수로 만들어서 사용도 가능!
-> 페이징 불가능하다는 단점 존재!
직접 jpql 로 작성하는게 번거로운데, 패치조인보다 sql이 적다는 이점 존재!
매번하는 new operation은 쿼리dsl 쓰면 정말 간단하게 해결 가능!
//이렇게하면 큰 쿼리 1개만 나가게돼
public List<OrderFlatDto> findAllByDto_flat(){
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, 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();
}
@GetMapping("/api/v6/orders")
public List<OrderFlatDto> ordersV6(){
return orderQueryRepository.findAllByDto_flat();
}
-> 이렇게하면 중복을 포함해서 반환이 돼
쿼리가 1번만 나갔다는 장점, 페이징을 할 수 있다는 장점도잇음
-> 벗 오더를 기준으로 페이징은안돼 orderitems기준으로돼
-> 따라서 제대로 페이징 못하는 단점!
-> flatDto 스펙으로 와서 orderQueryDto로 스펙이 안맞아
@GetMapping("/api/v6/orders")
public List<OrderFlatDto> 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());
}
@EqualsAndHashCode(of = "orderId") 이거 붙이면 오더아이디 기준으로 묶어서 중복 제거해줌
-> 오더를 기준으론 페이징이 안된다는 단점! 디비에서는 데이터가 중복돼서!