Intro
프로젝트를 진행하며 겪은 문제 상황과 해결 과정을 기록하는 글입니다.
이후 같은 문제가 발생할 경우 빠른 문제 해결을 위해 정리합니다.
상황 설명
쇼핑몰 프로젝트에서 상품을 주문하는 로직을 수행하던 중 재고 부족 예외가 터지면서 주문에 실패했다.
물론 해당 상품은 재고(quantity)가 충분한 상품이다.
Why?
OrderItem : orderItemDto.getQuantity: 0
OrderController : 주문 실패: {}
dressshop.exception.customException.ItemNotStockException: 재고가 부족하여 주문할 수 없습니다.
at dressshop.domain.order.OrderItem.createOrderItem(OrderItem.java:60) ~[main/:na]
at dressshop.service.order.OrderServiceImpl.toOrder(OrderServiceImpl.java:41) ~[main/:na]
//OrderItem
public static OrderItem createOrderItem(OrderItemDto orderItemDto, int count) {
if (orderItemDto.getItem() == null) {
throw new IllegalArgumentException("상품 정보가 없습니다.");
}
if (orderItemDto.getItem().getQuantity() < count) {
//orderItemDto.getQuantity: 0
log.info("orderItemDto.getQuantity: {}", orderItemDto.getItem().getQuantity());
throw new ItemNotStockException(); //"재고가 부족하여 주문할 수 없습니다."
}
return OrderItem.builder()
.item(orderItemDto.getItem())
.orderPrice(orderItemDto.getItem().getPrice())
.count(count)
.imgName(orderItemDto.getImgName())
.build();
}
오류 로그를 따라가보니 `orderItemDto.getItem().getQuantity() < count` 이 코드가 문제였고, 더 파보니 getQuantity()의 값이 0으로 세팅되어 있었다.
이를 확인하고 Repository 단에서 quantity 값을 제대로 불러오지 않은 것 같아 쿼리를 새로 짜 봤다.
@Override
public List<CartItemDto> findCartItemList(Long cartId) {
return query
.select(Projections.bean(CartItemDto.class,
cartItem.id,
cartItem.cart.id.as("cartId"),
cartItem.item.id.as("itemId"),
cartItem.item,
cartItem.count,
cartItem.item.quantity, //추가
itemImg.imgName))
.from(cartItem)
.leftJoin(cartItem.cart, cart)
.leftJoin(cartItem.item, item)
.leftJoin(item.itemImgs, itemImg)
.where(cartItem.cart.id.eq(cartId)
.and(itemImg.repImgYn.eq("Y")))
.fetch();
}
---> 실패
컨트롤러와 서비스 단에는 정상적으로 재고 값이 찍히는데, 이상하게 createOrderItem() 메서드에만 0 값이 들어가고 있었다.
타임리프 변수도 바꿔 보고, 서비스 단에서 직접 값도 주입해 보고, .... 이것저것 시도하다 결국 주문 로직 전체 리팩토링을 수행했다.........
//OrderController
@PostMapping
public String order(@Valid @ModelAttribute("orderForm") OrderDto orderDto,
@Valid @ModelAttribute("deliveryForm") DeliveryDto deliveryDto,
BindingResult bindingResult, Principal principal) {
if (bindingResult.hasErrors()) {
return "orders/order";
}
List<CartItemDto> cartItemList = cartService.getCartItemList(principal.getName());
Long orderId = orderService.order(cartItemList, deliveryDto, principal);
return "redirect:/order/orderComplete/" + orderId;
}
//OrderServiceImpl
//상품 주문
@Override
public Long order(List<CartItemDto> cartItemList, DeliveryDto deliveryDto, Principal principal) {
Member member = memberRepository.findByEmail(principal.getName());
Delivery delivery = deliveryDto.toEntity(deliveryDto, member);
Order order = Order.builder()
.member(member)
.delivery(delivery)
.address(new Address(deliveryDto.getCity(), deliveryDto.getStreet(), deliveryDto.getZipcode()))
.orderStatus(ORDER)
.totalPrice(cartItemList.stream()
.mapToInt(CartItemDto::getTotalPrice)
.sum())
.build();
orderRepository.save(order);
//주문 상품 저장
List<OrderItem> orderItemList = new ArrayList<>();
for (CartItemDto cartItemDto : cartItemList) {
OrderItem orderItem = OrderItem.builder()
.order(order)
.item(cartItemDto.getItem())
.orderPrice(cartItemDto.getItem().getPrice())
.count(cartItemDto.getCount())
.build();
orderItemList.add(orderItem);
Item item = orderItem.getItem();
item.decreaseStock(cartItemDto.getCount());
itemRepository.save(item);
}
order.addOrderItems(orderItemList);
//주문 완료 후 장바구니 제거
cartRepository.deleteByMember(member);
log.info("장바구니가 제거되었습니다.");
log.info("주문이 완료되었습니다. 주문번호={} 주문자={} 이메일={} 전화번호={} 주소={} {} {}",
order.getId(), member.getUsername(), member.getEmail(), deliveryDto.getTel(), deliveryDto.getStreet(), deliveryDto.getCity(), deliveryDto.getZipcode());
return order.getId();
}
//Item
public void decreaseStock(int orderCount) {
int restStock = this.quantity - orderCount;
if (restStock < 0) {
throw new ItemNotStockException();
}
this.quantity = restStock;
this.itemSellCount += orderCount;
if (restStock == 0) {
this.itemSellStatus = SOLD_OUT;
}
}
OrderItem 엔티티를 메서드가 아닌 빌더 패턴으로 생성하도록 변경하였다.
재고 감소 로직에서 재고 수량보다 더 많은 수를 주문하면 예외가 터지도록 구현하였다.
해결 완료
Item 데이터도 알맞게 업데이트 된 걸 볼 수 있다 ㅎㅎ!!!! (+2 item_sell_count / -2 quantity)
😑 결국 무엇이 문제였던 걸까?
리팩토링을 끝낸 상태라 이전 코드를 100% 원복은 못 하지만, 대충 복기해보면 다음과 비슷한 코드였다.
//OrderServiceImpl
//상품 주문
@Transactional
public Long toOrder(OrderItemDto orderItemDto, ... ) {
//어쩌구저쩌구
OrderItem orderItem = orderItem.createOrderItem(orderItemDto, orderItemDto.getCount());
...
}
//OrderItem
public static OrderItem createOrderItem(OrderItemDto orderItemDto, int count) {
if (orderItemDto.getItem() == null) {
throw new IllegalArgumentException("상품 정보가 없습니다.");
}
if (orderItemDto.getItem().getQuantity() < count) {
log.info("orderItemDto.getQuantity: {}", orderItemDto.getItem().getQuantity());
throw new ItemNotStockException(); //"재고가 부족하여 주문할 수 없습니다."
}
return OrderItem.builder()
.item(orderItemDto.getItem())
.orderPrice(orderItemDto.getItem().getPrice())
.count(count)
.imgName(orderItemDto.getImgName())
.build();
}
OrderItemDto를 제대로 가져오지 못 했는가?
-> 아니다. 위 서비스 단에서 quantity 값을 찍어 보았을 때 정상적인 값이 찍혔다.
분명한 건 orderItemDto 값은 서비스 단에 제대로 넘어 왔고, 이 orderItemDto가 OrderItem 엔티티에 넘어갈 때 무언가 문제가 생긴 것이다. 이 문제는 프로젝트를 진행하면서 계속 알아봐야 겠다..😥
'Trouble Shooting' 카테고리의 다른 글
[Spring Security] JWT 토큰이 계속 헤더에서 사라지는 오류 (임시(?) 해결) (0) | 2024.06.26 |
---|---|
[EC2] SSH - Host key verification failed. (0) | 2024.05.20 |
[Java, Spring] 주문 로직 중 NullPointerException 해결 (0) | 2024.05.19 |
[Spring Security 오류] 권한 접근 설정이 제대로 적용되지 않는 오류 (0) | 2024.05.19 |