컬렉션이 있을 때 어떻게 DTO로 조회하는지 알아보도록 하겠습니다.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderRepository orderRepository;
private final OrderQueryRepository orderQueryRepository;
@GetMapping("/api/v4/orders")
public List<OrderQueryDto> ordersV4() {
return orderQueryRepository.findOrderQueryDtos();
}
}
@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;
}
}
@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;
}
}
package jpabook.jpashop.repository.order.query; //...1
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
private final EntityManager em;
public List<OrderQueryDto> findOrderQueryDtos() {
List<OrderQueryDto> results = findOrders();
results.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return results;
}
//...2
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
//...3
"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() {
return em.createQuery(
//...2
"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();
}
}
OrderQueryRepository 와 OrderRepository 의 패키지를 분리했습니다.
즉, 관심사 분리하기 위해 패키지를 분리했다고 보면 됩니다. → 자세히
jpql 을 작성하더라도 컬렉션을 바로 넣을 수 없습니다.
사실 jpql 도 sql 을 사용하는 것과 비슷합니다. 그래서 데이터를 플랫하게 한 줄로 밖에 못 넣습니다.
그런데 OrderItem 같은 경우는 일대다(1:N)라서 데이터가 뻥튀기 되기 때문에 바로 DTO에 넣을 수 없습니다.
그래서 OrderItem 에 대한 부분은 따로 쿼리를 작성해야합니다. 그래서 루프를 돌면서 쿼리를 날리고 OrderItem 에 대한 값을 직접 넣어주고 있습니다.
OrderItem 에 대한 쿼리를 보면 orderId 를 가져오도록 oi.order.id
로 작성했습니다. 이러면 OrderItem 은 Order 를 참조하는게 아니라 DB 입장에서 orderId 를 FK 로 가지고 있기 때문에 바로 넣어줍니다.
물론 JSON 으로 응답할 때는 필요 없는 데이터라서 @JsonIgnore 를 했습니다.
실행을 하면 원하는대로 값이 노출 됩니다.
[
{
"orderId": 4,
"name": "userA",
"orderDate": "2024-04-18T16:43:05.649628",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderItems": [
{
"itemName": "JPA1 BOOK",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 20000,
"count": 2
}
]
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2024-04-18T16:43:05.770806",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 20000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 40000,
"count": 4
}
]
}
]