From 93023d15128d3f5ce211c6f4255d022f924e5b38 Mon Sep 17 00:00:00 2001 From: Soyun_p Date: Thu, 24 Apr 2025 16:31:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EC=9E=A5=EB=B0=94=EA=B5=AC=EB=8B=88=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EC=8B=9C=20List=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”— Resolves: #be/feat/319 --- .../cart/controller/CartController.java | 4 +- .../controller/Impl/CartControllerImpl.java | 6 +- .../com/jishop/cart/service/CartService.java | 2 +- .../cart/service/Impl/CartServiceImpl.java | 81 ++++++++++--------- .../jishop/common/exception/ErrorType.java | 3 +- 5 files changed, 54 insertions(+), 42 deletions(-) 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/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..7c4aea2d 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 @@ -14,6 +14,7 @@ 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 +41,53 @@ 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) { + 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); + } else { + // ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ์ด ์—†๋Š” ๊ฒฝ์šฐ + // ์žฌ๊ณ  ์ฒดํฌ + if (!stock.hasStock(requestQuantity)) + throw new DomainException(ErrorType.INSUFFICIENT_STOCK); + + cart = Cart.builder() + .user(user) + .saleProduct(saleProduct) + .quantity(requestQuantity) + .build(); + cartRepository.save(cart); } - - // 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 From f8e84925c19a426a65980405cb22bc8be6fde214 Mon Sep 17 00:00:00 2001 From: Soyun_p Date: Sat, 26 Apr 2025 21:56:19 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=82=99=EA=B4=80=EC=A0=81=20=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”— Resolves: #be/feat/319 --- .../java/com/jishop/cart/domain/Cart.java | 3 + .../cart/service/Impl/CartServiceImpl.java | 90 +++++++++++-------- 2 files changed, 58 insertions(+), 35 deletions(-) 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/Impl/CartServiceImpl.java b/backend/JiShop/src/main/java/com/jishop/cart/service/Impl/CartServiceImpl.java index 7c4aea2d..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,6 +11,7 @@ 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; @@ -42,42 +43,61 @@ public CartResponse getCart(User user) { @Override @Transactional public CartResponse addCartItem(User user, List addCartRequests) { - 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; + 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); + } } - - // isForced๊ฐ€ true์ธ ๊ฒฝ์šฐ, ๊ธฐ์กด ์ˆ˜๋Ÿ‰ + ์ƒˆ๋กœ์šด ์ˆ˜๋Ÿ‰์œผ๋กœ ์—…๋ฐ์ดํŠธ - int totalQuantity = cart.getQuantity() + requestQuantity; - - // ์žฌ๊ณ  ์ฒดํฌ - if (!stock.hasStock(totalQuantity)) - throw new DomainException(ErrorType.INSUFFICIENT_STOCK); - - cart.updateQuantity(totalQuantity); - } else { - // ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ์ด ์—†๋Š” ๊ฒฝ์šฐ - // ์žฌ๊ณ  ์ฒดํฌ - if (!stock.hasStock(requestQuantity)) - throw new DomainException(ErrorType.INSUFFICIENT_STOCK); - - cart = Cart.builder() - .user(user) - .saleProduct(saleProduct) - .quantity(requestQuantity) - .build(); - cartRepository.save(cart); + if(!cartsToSave.isEmpty()) + cartRepository.saveAll(cartsToSave); + + //์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚ฌ์œผ๋ฉด while๋ฌธ ํƒˆ์ถœ + break; + } catch(ObjectOptimisticLockingFailureException e){ + //์ถฉ๋Œ ๋‚ฌ์„ ๋•Œ + if(++attempt >= maxRetries) + throw new DomainException(ErrorType.CART_OPERATION_FAILED); } }