주문 기능

심규환·2022년 2월 8일
0

Shop

목록 보기
5/10

이번에는 상품 페이지에서 수량을 넣고 주문을 하는 코드입니다. 상품별 기타 옵션이나 쿠폰 사용 여부는 추후 직접 구현을 해보겠습니다.
먼저 상품의 id수량을 받을 Dto 객체를 만들어봅니다. @Valid를 사용하여 1차적으로 필터를 걸어줍니다.

DTO

OrderDto.Java

@Setter @Getter
public class OrderDto {

    @NotNull(message = "상품 아이디는 필수 입력값입니다.")
    private Long itemId;

    @Min(value = 1, message = "최소 주문 수량은 1개입니다.")
    @Max(value = 999, message = "최대 주문 수량은 999개입니다.")
    private int count;
}

Exception

OutOfStockException

예외처리는 exceptionHandler를 이용해서 하는 방법등 다양합니다. 여기서는 RuntimeException을 상속받아 실행 중에 재고 수량이 부족하다면 예외를 발생시키도록 새로 만들어보겠습니다.

public class OutOfStockException extends RuntimeException{
    public OutOfStockException(String message){
        super(message);
    }
}

Entity

Item.Java

주문을 할 때는 재고 수량에 맞게 주문하는 것이 중요합니다. 재고보다 많은 주문이 들어오면 에러를 반환하도록 설계하고 재고는 item 내부 필드이므로 내부 메서드로 재고-주문수량를 구현합니다.

public void removeStock(int stockNumber){
    int restStock = this.stockNumber - stockNumber;
    if(restStock < 0){
        throw new OutOfStockException("상품의 재고가 부족 합니다. (현재 재고 수량 : " + this.stockNumber + ")");
    }
    this.stockNumber = restStock;
}

OrderItem.Java

정적 생성자를 사용해서 매개변수로 itemcount 수를 받아서 OrderItem을 생성하게 합니다. Builder를 사용해서 OrderItem을 생성하고 item의 가격을 저장하고 갯수 * 가격은 따로 public 메서드로 제공합니다.
여기서 OrderItem을 생성해서 price 값을 넣을 때, 쿠폰, 시세 등 적용하여 넣는 방식으로 구현을 할 수 있습니다.
OrderItem 생성하고 item의 메서드인 removeStock을 사용하여 재고를 제거합니다.
따로 OrderItem을 Repository에 저장하지 않는 이유는 Order에서 OrderItem을 넣어서 저장하면 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)의 Cascade 설정에 의해 같이 저장이 되기 때문입니다.


public static OrderItem createOrderItem(Item item, int count){
    OrderItem orderItem = OrderItem.builder()
            .item(item)
            .count(count)
            .orderPrice(item.getPrice())
            .build();

    item.removeStock(count);
    return orderItem;
}

public int getTotalPrice(){
    return orderPrice * count;
}

Order.Java

Order 내부에 OrderItems가 OneToMany로 양방향으로 선언되어 있기 때문에 양쪽에 값을 넣을 메서드를 하나 만들어 줍니다. (addOrderItem(OrderItem orderItem))
그리고 Order 생성 또한 객체 내부에서 책임을 가질 수 있도록 정적 생성자를 제공합니다. 매개변수는 member, List orderItemList를 줍니다.
Order를 member와 비어있는 OrderItems, 주문 상태, 주문 시간을 전달하여 생성합니다. 메시지로 넘어온 orderItemList들을 for문으로 하나씩 addOrderItem을 사용하여 orderItems에 넣어줍니다.

public static Order createOrder(Member member, List<OrderItem> orderItemList){
    List<OrderItem> orderItems = new ArrayList<>();
    Order order = Order.builder()
            .member(member)
            .orderStatus(OrderStatus.ORDER)
            .orderDate(LocalDateTime.now())
            .orderItems(orderItems).
            build();

    for (OrderItem orderItem : orderItemList) {
        order.addOrderItem(orderItem);
    }
    return order;
}

public int getTotalPrice(){
    int totalPrice = 0;
    for(OrderItem orderItem : orderItems){
        totalPrice +=orderItem.getTotalPrice();
    }
    return totalPrice;
}

Service

OrderService.Java

Controller에서 전달받은 email을 통해 member와 Dto 내부의 item_id를 사용해서 item을 가져옵니다. 그리고 item과 Dto의 count를 사용해서 OrderItem를 생성한 뒤, order를 만들고 저장합니다.

public Long order(OrderDto orderDto, String email){
    Item item = itemRepository.findById(orderDto.getItemId()).orElseThrow(EntityNotFoundException::new);
    Member member = memberRepository.findByEmail(email);

    List<OrderItem> orderItemList = new ArrayList<>();
    OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());

    orderItemList.add(orderItem);

    Order order = Order.createOrder(member, orderItemList);
    orderRepository.save(order);
    return order.getId();
}

Controller

OrderController

새로 고침 없이 비동기 방식으로 처리하기 위해 @ResponseBody를 사용합니다.
먼저 bindingReulst에 에러가 있다면, String 값들을 모아줄 수 있는 StringBuiler를 선언하고 @Valid의 에러 정보가 담겨져 있는 BindingResult의 필드값들을 모두 가져와서 StringBuiler에 넣어줍니다. 그리고 ResponseEntity를 생성하여 반환해 줍니다.

Principal는 Security에서 제공하는 객체입니다. Principal 말고도 Authentication 객체를 사용해도 무관합니다.Principal은 현재 접속한 사용자의 기본 정보를 제공 받습니다. 이때의 값은 UserDetail 값으로 전달받은 username값에 해당합니다. (UserDetailsService의 구현체 내부 loadUserByUserName 메서드)

@PostMapping("/order")
public @ResponseBody ResponseEntity order(@RequestBody @Valid OrderDto orderDto, BindingResult bindingResult,
                                              Principal principal){
    if(bindingResult.hasErrors()){
        StringBuilder sb = new StringBuilder();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        for(FieldError fieldError: fieldErrors){
            sb.append(fieldError.getDefaultMessage());
        }
        return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);
    }

    String email = principal.getName();
    Long orderId;
    try{
        orderId = orderService.order(orderDto,email);
    }catch(Exception e){
        return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
    return new ResponseEntity<Long>(orderId, HttpStatus.OK);
}
profile
장생농씬가?

0개의 댓글