오늘은 주문 등록 및 조회를 개발했다. 김영한님의 인프런 강의를 보고도 꽤 어려웠는데, 강의를 보지 않았더라면 정말 많이 돌아갔을 것 같다. 그럼 이제 내 코드를 분석해보겠다.
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
private final ItemService itemService;
private final DeliveryService deliveryService;
@PostMapping("/api/order/registration")
public ResponseEntity<OrderDto> orderRegistration(@RequestBody RequestOrderDto requestOrderDto) {
Long memberId = requestOrderDto.getMemberId();
// memberID null 값 전달되는 것 해결 -> @RequsetBody 어노테이션을 입력해야 Json 데이터가 DTO에 매핑이된다.
if (memberId == null) {
throw new IllegalArgumentException("Member ID cannot be null");
}
// id를 통해서 member를 찾고
Member member = memberRepository.findById(memberId);
// address 정보를 입력후 저장
Address address = new Address(requestOrderDto.getCity(), requestOrderDto.getStreet(), requestOrderDto.getZipcode());
// address 정보를 저장후 배송에 저장
Delivery delivery = new Delivery();
delivery.setAddress(address);
deliveryService.save(delivery);
List<OrderItem> orderItems = requestOrderDto.getItems().stream()
.map(itemRequest -> {
Item item = itemService.findById(itemRequest.getItemId());
return OrderItem.createOrderItem(item, item.getPrice(), itemRequest.getQuantity());
})
.collect(toList());
Order order = Order.creatOrder(member, delivery, orderItems.toArray(new OrderItem[0]));
orderRepository.save(order);
OrderDto orderDto = new OrderDto(order);
return ResponseEntity.ok(orderDto);
}
@GetMapping("/api/orders")
public List<OrderDto> ordersV3_page(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "100") int limit) {
List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);
List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))
.collect(toList());
return result;
}
@Data
static class OrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate; //주문시간
private OrderStatus orderStatus;
private Address address;
private List<OrderItemDto> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
orderItems = order.getOrderItems().stream()
.map(orderItem -> new OrderItemDto(orderItem))
.collect(toList());
}
}
@Data
static class RequestOrderDto {
private Long memberId;
private List<OrderItemRequest> items;
private String city;
private String street;
private String zipcode;
@Data
static class OrderItemRequest {
private Long itemId;
private Integer quantity;
}
}
@Data
static class OrderItemDto { // 아이템을 DTO로 묶어야함 -> DTO 안에도 엔티티가 유출되어서는 안된다.
private String itemName;//상품 명
private int orderPrice; //주문 가격
private int count; //주문 수량
public OrderItemDto(OrderItem orderItem) {
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}
}
}
먼저 위 코드에서 requestOrderDto를 숙지하자
주문을 등록하기 위한 조건을 살펴볼때 내가 생각했던 것은
1. 주문을 한 사람 -> memberId
2. 주문된 상품(들) -> OrderItemRequest를 구성하여 itemId와 quantity를 받음
3. 주소 -> 주문정보에 어디로 배송되는지 정보도 있으면 좋을 것 같았기 때문
위와 같은 정보를 받아야 주문이 정상적으로 등록될 수 있다고 생각했다.
그래서 requestOrderDto를 위와같이 구성.
다음 orderRegistration 메서드의 로직을 살펴보면
1. 받아온 memberId를 통해서 member정보 데이터베이스에서 찾음
2. 받아온 주소를 저장하여 Delivery 데이터 저장.
3. 받아온 아이템들의 id를 통해 데이터를 받아오고, createOrderItem() 메서드를 통해서 List<"OrderItem">의 형태로 받음
4. createOrder() 메서드를 통해 위과정에서 생성한 member, delivery, orderItems 배열 들을 조합한 주문을 등록 후 데이터베이스에 저장
5. 엔티티들 직접 전달하면좋지 않으므로 엔티티를 orderDto에 매핑하여 객체 반환.
위와 같이 주문등록을 구성하였다.
이때 memberId가 null 값이면 exception을 터뜨리는 로직이 있는데 이는 메서드에서 파라미터를 받을때 @RequestBody 어노테이션을 저장하지 않아 발생한 문제였다. ㅎㅎ
위 메서드는 데이터 베이스 내의 모든 주문 데이터를 Dto로 전환하여 반환하는 메서드로 김영한 강사님께서 알려주신 비법이다.
public List<Order> findAllWithMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
findAllWithMemberDelivery 메서드이다. 위 메서드에서 확인하면 member와 delivery에 관련한 정보만 가져오는 것을 볼 수 있는데 이는 order와 member, delivery는 ToOne 관계이기 때문에 row 수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않기 때문이다.
그리고 프론트엔드에서 Get 요청시 offset 과 limit을 설정할 수 있게하여 원하는 데이터의 크기만큼 데이터베이스에서 가져올 수 있게한다.
이를 통해 컬렉션 관계를 최적화 할 수 있다.
사실 주문 관련한 로직들은 다 짰었는데, 오늘 테스트를 통해서 돌아가는 것을 확인했고 그래서 후기를 쓰게 되었다. 로직을 짜는 것은 오래걸리지 않았는데, requestDto에서 멤버변수의 이름을 잘못 설정하여 값을 null 값을 받아온다던지 하는 내 실수들로 시간을 꽤 먹은 것 같다.
이제 진짜 개강까지 2주도 안남았는데 한번 불태워보도록 하자.