From 03a04689825c242a2da37126d3ca96195fef7aa4 Mon Sep 17 00:00:00 2001 From: marinesnow34 Date: Fri, 17 Nov 2023 16:51:24 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Config:=20cart=20=EC=82=AD=EC=A0=9C,=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/readyvery/readyverydemo/domain/Cart.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/readyvery/readyverydemo/domain/Cart.java b/src/main/java/com/readyvery/readyverydemo/domain/Cart.java index 4c20e59..ecb1650 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/Cart.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/Cart.java @@ -43,6 +43,12 @@ public class Cart extends BaseTimeEntity { @JoinColumn(name = "store_idx") private Store store; + @Column(nullable = false, columnDefinition = "BOOLEAN default false") + private Boolean isOrdered; + + @Column(nullable = false, columnDefinition = "BOOLEAN default false") + private Boolean isDeleted; + // 장바구니 장바구니 아이템 연관관계 매핑 @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL) private List cartItems = new ArrayList(); From 69a472c5ceff1647e3d0f1b9ec378dd5973f39a0 Mon Sep 17 00:00:00 2001 From: marinesnow34 Date: Fri, 17 Nov 2023 21:51:10 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Config:=20table=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=95=88=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b187ca2..8802c0d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,8 @@ spring.datasource.url=jdbc:mysql://localhost:3306/readyvery spring.datasource.username=root spring.datasource.password=12345678 -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true spring.jackson.serialization.fail-on-empty-beans=false # JWT Configuration jwt.secretKey=Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkw From 7bd1c6f6974164f268fe548bd5114c67ab83ab8a Mon Sep 17 00:00:00 2001 From: marinesnow34 Date: Fri, 17 Nov 2023 21:51:47 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Config:=20domain=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/readyvery/readyverydemo/domain/Cart.java | 14 ++++++++++---- .../readyvery/readyverydemo/domain/CartItem.java | 3 +++ .../readyvery/readyverydemo/domain/CartOption.java | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/readyvery/readyverydemo/domain/Cart.java b/src/main/java/com/readyvery/readyverydemo/domain/Cart.java index ecb1650..c082a49 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/Cart.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/Cart.java @@ -16,6 +16,7 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,6 +27,7 @@ @Table(name = "CART") @AllArgsConstructor @Slf4j +@Builder public class Cart extends BaseTimeEntity { @Id @@ -34,6 +36,7 @@ public class Cart extends BaseTimeEntity { private Long id; // 장바구니 유저 연관관계 매핑 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_idx") private UserInfo userInfo; @@ -43,13 +46,16 @@ public class Cart extends BaseTimeEntity { @JoinColumn(name = "store_idx") private Store store; - @Column(nullable = false, columnDefinition = "BOOLEAN default false") - private Boolean isOrdered; + @Column + @Builder.Default + private Boolean isOrdered = false; - @Column(nullable = false, columnDefinition = "BOOLEAN default false") - private Boolean isDeleted; + @Column + @Builder.Default + private Boolean isDeleted = false; // 장바구니 장바구니 아이템 연관관계 매핑 + @Builder.Default @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL) private List cartItems = new ArrayList(); diff --git a/src/main/java/com/readyvery/readyverydemo/domain/CartItem.java b/src/main/java/com/readyvery/readyverydemo/domain/CartItem.java index 9802cac..a9fd06c 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/CartItem.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/CartItem.java @@ -16,6 +16,7 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,6 +27,7 @@ @Table(name = "CART_ITEM") @AllArgsConstructor @Slf4j +@Builder public class CartItem extends BaseTimeEntity { @Id @@ -48,6 +50,7 @@ public class CartItem extends BaseTimeEntity { private Cart cart; // 장바구니 아이템 - 장바구니 옵션 연관관계 매핑 + @Builder.Default @OneToMany(mappedBy = "cartItem", cascade = CascadeType.ALL) private List cartOptions = new ArrayList(); diff --git a/src/main/java/com/readyvery/readyverydemo/domain/CartOption.java b/src/main/java/com/readyvery/readyverydemo/domain/CartOption.java index 41d2079..9190e94 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/CartOption.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/CartOption.java @@ -11,6 +11,7 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +22,7 @@ @Table(name = "CART_OPTION") @AllArgsConstructor @Slf4j +@Builder public class CartOption extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) From cc93fd11561743ffc0bba62b20257441fdb2f19a Mon Sep 17 00:00:00 2001 From: marinesnow34 Date: Fri, 17 Nov 2023 21:52:18 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/CartItemRepository.java | 8 ++++++++ .../domain/repository/CartOptionRepository.java | 8 ++++++++ .../domain/repository/CartRepository.java | 12 ++++++++++++ .../domain/repository/FoodieOptionRepository.java | 8 ++++++++ .../readyverydemo/src/order/dto/CartAddReq.java | 13 +++++++++++++ .../readyverydemo/src/order/dto/CartAddRes.java | 10 ++++++++++ .../readyverydemo/src/order/dto/OrderMapper.java | 7 +++++++ 7 files changed, 66 insertions(+) create mode 100644 src/main/java/com/readyvery/readyverydemo/domain/repository/CartItemRepository.java create mode 100644 src/main/java/com/readyvery/readyverydemo/domain/repository/CartOptionRepository.java create mode 100644 src/main/java/com/readyvery/readyverydemo/domain/repository/CartRepository.java create mode 100644 src/main/java/com/readyvery/readyverydemo/domain/repository/FoodieOptionRepository.java create mode 100644 src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddReq.java create mode 100644 src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddRes.java diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/CartItemRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartItemRepository.java new file mode 100644 index 0000000..3860fb1 --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartItemRepository.java @@ -0,0 +1,8 @@ +package com.readyvery.readyverydemo.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.CartItem; + +public interface CartItemRepository extends JpaRepository { +} diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/CartOptionRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartOptionRepository.java new file mode 100644 index 0000000..ce61d4e --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartOptionRepository.java @@ -0,0 +1,8 @@ +package com.readyvery.readyverydemo.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.CartOption; + +public interface CartOptionRepository extends JpaRepository { +} diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/CartRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartRepository.java new file mode 100644 index 0000000..e2cd86f --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/CartRepository.java @@ -0,0 +1,12 @@ +package com.readyvery.readyverydemo.domain.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.Cart; +import com.readyvery.readyverydemo.domain.UserInfo; + +public interface CartRepository extends JpaRepository { + Optional findByUserInfoAndIsDeletedFalse(UserInfo userInfo); +} diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/FoodieOptionRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/FoodieOptionRepository.java new file mode 100644 index 0000000..e820fcf --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/FoodieOptionRepository.java @@ -0,0 +1,8 @@ +package com.readyvery.readyverydemo.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.FoodieOption; + +public interface FoodieOptionRepository extends JpaRepository { +} diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddReq.java b/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddReq.java new file mode 100644 index 0000000..3da1c57 --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddReq.java @@ -0,0 +1,13 @@ +package com.readyvery.readyverydemo.src.order.dto; + +import java.util.List; + +import lombok.Getter; + +@Getter +public class CartAddReq { + private Long storeId; + private Long foodieId; + private List options; + private Long count; +} diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddRes.java b/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddRes.java new file mode 100644 index 0000000..f4d0d15 --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/src/order/dto/CartAddRes.java @@ -0,0 +1,10 @@ +package com.readyvery.readyverydemo.src.order.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CartAddRes { + private Long cartItemId; +} diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java b/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java index bd01fb6..106ffb0 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; +import com.readyvery.readyverydemo.domain.CartItem; import com.readyvery.readyverydemo.domain.Foodie; import com.readyvery.readyverydemo.domain.FoodieOption; import com.readyvery.readyverydemo.domain.FoodieOptionCategory; @@ -56,4 +57,10 @@ private FoodyOptionDto foodyOptionToOptionDto(FoodieOption option) { .price(option.getPrice()) .build(); } + + public CartAddRes cartToCartAddRes(CartItem cartItem) { + return CartAddRes.builder() + .cartItemId(cartItem.getId()) + .build(); + } } From dabacc00ac89a5b5912c7280d165244b9e338a06 Mon Sep 17 00:00:00 2001 From: marinesnow34 Date: Fri, 17 Nov 2023 21:52:29 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/ExceptionCode.java | 9 +- .../src/order/OrderController.java | 13 ++ .../readyverydemo/src/order/OrderService.java | 5 + .../src/order/OrderServiceImpl.java | 137 ++++++++++++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java b/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java index ea9e09f..ca72e27 100644 --- a/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java +++ b/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java @@ -5,11 +5,12 @@ @Getter public enum ExceptionCode { STORE_NOT_FOUND(404, "Store does not exists."), - USER_NOT_FOUND(404, "User does not exists."), - - FOODY_NOT_FOUND(404, "Foody does not exists."); - + FOODY_NOT_FOUND(404, "Foody does not exists."), + FOODY_NOT_IN_STORE(400, "Foody does not exists in store."), + INVALID_OPTION_COUNT(400, "Invalid option count."), + INVALID_OPTION(400, "Invalid option."), + OPTION_NOT_FOUND(404, "Option does not exists."); private int status; private String message; diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java index a54332f..cd052a9 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java @@ -2,12 +2,18 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.readyvery.readyverydemo.security.jwt.dto.CustomUserDetails; +import com.readyvery.readyverydemo.src.order.dto.CartAddReq; +import com.readyvery.readyverydemo.src.order.dto.CartAddRes; import com.readyvery.readyverydemo.src.order.dto.FoodyDetailRes; import lombok.RequiredArgsConstructor; @@ -26,4 +32,11 @@ public ResponseEntity getFoody( FoodyDetailRes foodyDetailRes = orderService.getFoody(storeId, foodyId, inout); return new ResponseEntity<>(foodyDetailRes, HttpStatus.OK); } + + @PostMapping("/cart") + public ResponseEntity addCart(@AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody CartAddReq cartAddReq) { + CartAddRes cartAddRes = orderService.addCart(userDetails, cartAddReq); + return new ResponseEntity<>(cartAddRes, HttpStatus.OK); + } } diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java index e19215c..e478932 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java @@ -1,7 +1,12 @@ package com.readyvery.readyverydemo.src.order; +import com.readyvery.readyverydemo.security.jwt.dto.CustomUserDetails; +import com.readyvery.readyverydemo.src.order.dto.CartAddReq; +import com.readyvery.readyverydemo.src.order.dto.CartAddRes; import com.readyvery.readyverydemo.src.order.dto.FoodyDetailRes; public interface OrderService { FoodyDetailRes getFoody(Long storeId, Long foodyId, Long inout); + + CartAddRes addCart(CustomUserDetails userDetails, CartAddReq cartAddReq); } diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java index a234c6f..4306f08 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java @@ -1,11 +1,31 @@ package com.readyvery.readyverydemo.src.order; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + import org.springframework.stereotype.Service; +import com.readyvery.readyverydemo.domain.Cart; +import com.readyvery.readyverydemo.domain.CartItem; +import com.readyvery.readyverydemo.domain.CartOption; import com.readyvery.readyverydemo.domain.Foodie; +import com.readyvery.readyverydemo.domain.FoodieOption; +import com.readyvery.readyverydemo.domain.FoodieOptionCategory; +import com.readyvery.readyverydemo.domain.Store; +import com.readyvery.readyverydemo.domain.UserInfo; +import com.readyvery.readyverydemo.domain.repository.CartItemRepository; +import com.readyvery.readyverydemo.domain.repository.CartOptionRepository; +import com.readyvery.readyverydemo.domain.repository.CartRepository; +import com.readyvery.readyverydemo.domain.repository.FoodieOptionRepository; import com.readyvery.readyverydemo.domain.repository.FoodieRepository; +import com.readyvery.readyverydemo.domain.repository.StoreRepository; +import com.readyvery.readyverydemo.domain.repository.UserRepository; import com.readyvery.readyverydemo.global.exception.BusinessLogicException; import com.readyvery.readyverydemo.global.exception.ExceptionCode; +import com.readyvery.readyverydemo.security.jwt.dto.CustomUserDetails; +import com.readyvery.readyverydemo.src.order.dto.CartAddReq; +import com.readyvery.readyverydemo.src.order.dto.CartAddRes; import com.readyvery.readyverydemo.src.order.dto.FoodyDetailRes; import com.readyvery.readyverydemo.src.order.dto.OrderMapper; @@ -14,7 +34,13 @@ @Service @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { + private final CartRepository cartRepository; + private final CartItemRepository cartItemRepository; + private final CartOptionRepository cartOptionRepository; private final FoodieRepository foodieRepository; + private final FoodieOptionRepository foodieOptionRepository; + private final UserRepository userRepository; + private final StoreRepository storeRepository; private final OrderMapper orderMapper; @Override @@ -24,4 +50,115 @@ public FoodyDetailRes getFoody(Long storeId, Long foodyId, Long inout) { ); return orderMapper.foodieToFoodyDetailRes(foodie, inout); } + + @Override + public CartAddRes addCart(CustomUserDetails userDetails, CartAddReq cartAddReq) { + UserInfo user = getUserInfo(userDetails); + Store store = getStore(cartAddReq.getStoreId()); + Foodie foodie = getFoody(cartAddReq.getFoodieId()); + + verifyFoodieInStore(store, foodie); + verifyCartAddReq(foodie, cartAddReq); + + Cart cart = cartRepository.findByUserInfoAndIsDeletedFalse(user).orElseGet(() -> makeCart(user, store)); + CartItem cartItem = makeCartItem(cart, foodie, cartAddReq.getCount()); + List cartOptions = cartAddReq.getOptions().stream() + .map(option -> makeCartOption(cartItem, option)) + .toList(); + + cartRepository.save(cart); + cartItemRepository.save(cartItem); + cartOptionRepository.saveAll(cartOptions); + + return orderMapper.cartToCartAddRes(cartItem); + } + + private CartOption makeCartOption(CartItem cartItem, Long option) { + FoodieOption foodieOption = getFoodieOption(option); + return CartOption.builder() + .cartItem(cartItem) + .foodieOption(foodieOption) + .build(); + } + + private FoodieOption getFoodieOption(Long option) { + return foodieOptionRepository.findById(option).orElseThrow( + () -> new BusinessLogicException(ExceptionCode.OPTION_NOT_FOUND) + ); + } + + private CartItem makeCartItem(Cart cart, Foodie foodie, Long count) { + return CartItem.builder() + .cart(cart) + .foodie(foodie) + .count(count) + .build(); + } + + private Cart makeCart(UserInfo user, Store store) { + return Cart.builder() + .userInfo(user) + .store(store) + .build(); + } + + private UserInfo getUserInfo(CustomUserDetails userDetails) { + return userRepository.findById(userDetails.getId()).orElseThrow( + () -> new BusinessLogicException(ExceptionCode.USER_NOT_FOUND) + ); + } + + private Store getStore(Long storeId) { + return storeRepository.findById(storeId).orElseThrow( + () -> new BusinessLogicException(ExceptionCode.STORE_NOT_FOUND) + ); + } + + private void verifyCartAddReq(Foodie foodie, CartAddReq cartAddReq) { + verifyOption(foodie, cartAddReq.getOptions()); + verifyEssentialOption(foodie, cartAddReq.getOptions()); + } + + private Foodie getFoody(Long foodieId) { + return foodieRepository.findById(foodieId).orElseThrow( + () -> new BusinessLogicException(ExceptionCode.FOODY_NOT_FOUND) + ); + } + + private void verifyFoodieInStore(Store store, Foodie foodie) { + boolean isFoodieInStore = foodie.getFoodieCategory().getStore().equals(store); + if (!isFoodieInStore) { + throw new BusinessLogicException(ExceptionCode.FOODY_NOT_IN_STORE); + } + } + + private void verifyOption(Foodie foodie, List opotions) { + // foodie안에 있는 옵션들을 Set으로 만들기 + Set optionSet = foodie.getFoodieOptionCategory().stream() + .flatMap(foodieOptionCategory -> foodieOptionCategory.getFoodieOptions().stream()) + .map(FoodieOption::getId) + .collect(Collectors.toSet()); + + // 옵션들이 올바른지 확인 + opotions.stream() + .filter(option -> !optionSet.contains(option)) + .findAny() + .ifPresent(option -> { + throw new BusinessLogicException(ExceptionCode.INVALID_OPTION); + }); + } + + private void verifyEssentialOption(Foodie foodie, List options) { + foodie.getFoodieOptionCategory().stream() + .filter(FoodieOptionCategory::isRequired) + .collect(Collectors.toSet()).stream() + .filter(foodieOptionCategory -> foodieOptionCategory.getFoodieOptions().stream() + .map(FoodieOption::getId) + .filter(options::contains) + .count() != 1) // 필수 값이 1개가 아닌 경우 + .findAny() + .ifPresent(foodieOptionCategory -> { + throw new BusinessLogicException(ExceptionCode.INVALID_OPTION_COUNT); + }); + } }