diff --git a/backend/JiShop/src/main/java/com/jishop/cart/controller/CartController.java b/backend/JiShop/src/main/java/com/jishop/cart/controller/CartController.java index 5394ba8d..3a4794e2 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/controller/CartController.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/controller/CartController.java @@ -8,6 +8,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestParam; +import java.util.List; + @Tag(name = "장바구니 API") public interface CartController { @@ -15,7 +17,7 @@ public interface CartController { ResponseEntity getCartItems(User user); @Operation(summary = "회원 장바구니 추가", description = "회원이 장바구니에 물건을 담을 때 사용되는 API") - ResponseEntity addCartItem(User user, AddCartRequest request); + ResponseEntity addCartItem(User user, List request); @Operation(summary = "회원 장바구니 수량 변경", description = "회원이 장바구니 내에서 수량 변경을 할 때 사용되는 API") ResponseEntity updateCartItem(User user, UpdateCartRequest request); diff --git a/backend/JiShop/src/main/java/com/jishop/cart/controller/Impl/CartControllerImpl.java b/backend/JiShop/src/main/java/com/jishop/cart/controller/Impl/CartControllerImpl.java index e87d4ae9..dbee2c51 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/controller/Impl/CartControllerImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/controller/Impl/CartControllerImpl.java @@ -36,10 +36,10 @@ public ResponseEntity getCartItems(@CurrentUser User user) { //장바구니 상품 추가 @Override @PostMapping - public ResponseEntity addCartItem(@CurrentUser User user, @RequestBody @Valid AddCartRequest request) { - CartDetailResponse cartDetailResponse = cartService.addCartItem(user, request); + public ResponseEntity addCartItem(@CurrentUser User user, @RequestBody List request) { + CartResponse cartResponse = cartService.addCartItem(user, request); - return ResponseEntity.ok(cartDetailResponse); + return ResponseEntity.ok(cartResponse); } //장바구니 상품 업데이트 diff --git a/backend/JiShop/src/main/java/com/jishop/cart/domain/Cart.java b/backend/JiShop/src/main/java/com/jishop/cart/domain/Cart.java index c310ae12..17d73489 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/domain/Cart.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/domain/Cart.java @@ -25,6 +25,9 @@ public class Cart extends BaseEntity { private int quantity; + @Version + private Long version; + @Builder public Cart(User user, SaleProduct saleProduct, int quantity) { this.user = user; diff --git a/backend/JiShop/src/main/java/com/jishop/cart/service/CartService.java b/backend/JiShop/src/main/java/com/jishop/cart/service/CartService.java index 08ef0f77..1a53abbf 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/service/CartService.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/service/CartService.java @@ -7,7 +7,7 @@ public interface CartService { CartResponse getCart(User user); - CartDetailResponse addCartItem(User user, AddCartRequest addCartRequest); + CartResponse addCartItem(User user, List addCartRequest); CartDetailResponse updateCart(User user, UpdateCartRequest updateCartRequest); void removeCartItem(User user, DeleteCartRequest deleteCartRequest); CartResponse getGuestCart(List guestCartRequest); diff --git a/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java index 632b6ad5..2745decb 100644 --- a/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java +++ b/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java @@ -11,9 +11,11 @@ import com.jishop.saleproduct.repository.SaleProductRepository; import com.jishop.stock.domain.Stock; import lombok.RequiredArgsConstructor; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -40,45 +42,72 @@ public CartResponse getCart(User user) { //장바구니 상품 추가 @Override @Transactional - public CartDetailResponse addCartItem(User user, AddCartRequest addCartRequest) { - SaleProduct saleProduct = saleProductRepository.findById(addCartRequest.saleProductId()) - .orElseThrow(()->new DomainException(ErrorType.PRODUCT_NOT_FOUND)); - - int requestQuantity = addCartRequest.quantity(); - Stock stock = saleProduct.getStock(); - - Cart cart = cartRepository.findByUserAndSaleProduct(user, saleProduct).orElse(null); - - // 이미 장바구니에 상품이 존재하는 경우 - if (cart != null) { - // isForced가 false인 경우, 기존 상품 정보만 반환하고 수량은 업데이트하지 않음 - if (!addCartRequest.isForced()) { - return CartDetailResponse.of(cart, true); + public CartResponse addCartItem(User user, List addCartRequests) { + int maxRetries = 3; + int attempt = 0; + + while(true){ + try{ + List cartsToSave = new ArrayList<>(); + + for (AddCartRequest request : addCartRequests) { + SaleProduct saleProduct = saleProductRepository.findById(request.saleProductId()) + .orElseThrow(() -> new DomainException(ErrorType.PRODUCT_NOT_FOUND)); + + int requestQuantity = request.quantity(); + Stock stock = saleProduct.getStock(); + + Cart cart = cartRepository.findByUserAndSaleProduct(user, saleProduct).orElse(null); + + // 이미 장바구니에 상품이 존재하는 경우 + if (cart != null) { + // isForced가 false인 경우, 수량을 업데이트하지 않음 + if (!request.isForced()) { + continue; + } + + // isForced가 true인 경우, 기존 수량 + 새로운 수량으로 업데이트 + int totalQuantity = cart.getQuantity() + requestQuantity; + + // 재고 체크 + if (!stock.hasStock(totalQuantity)) + throw new DomainException(ErrorType.INSUFFICIENT_STOCK); + + cart.updateQuantity(totalQuantity); + cartsToSave.add(cart); + } else { + // 장바구니에 상품이 없는 경우 + // 재고 체크 + if (!stock.hasStock(requestQuantity)) + throw new DomainException(ErrorType.INSUFFICIENT_STOCK); + + cart = Cart.builder() + .user(user) + .saleProduct(saleProduct) + .quantity(requestQuantity) + .build(); + cartsToSave.add(cart); + } + } + if(!cartsToSave.isEmpty()) + cartRepository.saveAll(cartsToSave); + + //성공적으로 끝났으면 while문 탈출 + break; + } catch(ObjectOptimisticLockingFailureException e){ + //충돌 났을 때 + if(++attempt >= maxRetries) + throw new DomainException(ErrorType.CART_OPERATION_FAILED); } - - // isForced가 true인 경우, 기존 수량 + 새로운 수량으로 업데이트 - int totalQuantity = cart.getQuantity() + requestQuantity; - - // 재고 체크 - if(!stock.hasStock(totalQuantity)) - throw new DomainException(ErrorType.INSUFFICIENT_STOCK); - - cart.updateQuantity(requestQuantity); - } else { - // 장바구니에 상품이 없는 경우 - // 재고 체크 - if(!stock.hasStock(requestQuantity)) - throw new DomainException(ErrorType.INSUFFICIENT_STOCK); - - cart = Cart.builder() - .user(user) - .saleProduct(saleProduct) - .quantity(requestQuantity) - .build(); - cartRepository.save(cart); } - return CartDetailResponse.of(cart, false); + // 모든 상품을 추가/업데이트한 후 전체 장바구니 정보를 반환 + List updatedCarts = cartRepository.findCartsWithProductAndOptionByUser(user); + List cartDetailResponses = updatedCarts.stream() + .map(cart -> CartDetailResponse.of(cart, false)) + .toList(); + + return CartResponse.of(cartDetailResponses); } // 장바구니 수량 수정 diff --git a/backend/JiShop/src/main/java/com/jishop/common/exception/ErrorType.java b/backend/JiShop/src/main/java/com/jishop/common/exception/ErrorType.java index 6e911303..dc7f5fb1 100644 --- a/backend/JiShop/src/main/java/com/jishop/common/exception/ErrorType.java +++ b/backend/JiShop/src/main/java/com/jishop/common/exception/ErrorType.java @@ -66,6 +66,7 @@ public enum ErrorType { // CART CART_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니 상품을 찾을 수 없습니다."), + CART_OPERATION_FAILED(HttpStatus.CONFLICT, "장바구니 처리 중 오류가 발생했습니다."), // VALIDATION VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "값을 잘못 입력했습니다."), @@ -84,7 +85,7 @@ public enum ErrorType { DEFAULT_ADDRESS_REQUIRED(HttpStatus.NOT_FOUND, "기본 배송지가 꼭 필요합니다."), // REDISSON - LOCK_ACQUISITION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "주문 저리 중 잠금 획득에 실패했습니다. 잠시 후 시도해주세요"), + LOCK_ACQUISITION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "주문 처리 중 잠금 획득에 실패했습니다. 잠시 후 시도해주세요"), CONCURRENT_ORDER_PROCESSING(HttpStatus.INTERNAL_SERVER_ERROR, "동시에 너무 많은 주문이 처리되고 있습니다. 잠시 후 다시 시도해주세요"), // QUESTION