1. 동시성 문제가 발생한 부분

		@Transactional
    public void orderProduct(Long memberId) {
        Member member = memberRepository.findById(memberId).orElseThrow(
                () -> new CustomException(BAD_REQUEST));
        List<Basket> baskets = basketQueryRepository.findBasketByMemberId(memberId);
        String orderNum = UUID.randomUUID().toString();
        LocalDateTime orderTime = LocalDateTime.now();
        for (Basket basket : baskets) {
            Orders orders = Orders.builder()
                    .orderNum(orderNum)
                    .orderTime(orderTime)
                    .productNum(basket.getProductQuantity())
                    .productPrice(basket.getProducts().getPrice())
                    .orderStatus(OrderStatus.배송준비)
                    .member(member)
                    .products(basket.getProducts()).build();
            validateStock(basket.getProducts(), basket);
            decreaseProductStock(basket.getProducts(), basket.getProductQuantity());
            updateProductStatus(basket.getProducts());
            orderRepository.save(orders);
		        }
		    }
  1. 동시에 1,000개의 스레드가 로직을 실행했을 때 동일한 데이터의 Product의 데이터를 불러옴
  2. 상품의 재고가 1,000개라고 했을 때 1,000개의 스레드 모두 1,000개에서 1개를 구매 처리했기 때문에 999개의 재고로 업데이트 하게 됨
  3. 결과적으로 Order는 정상적으로 Insert 되었지만 제품의 재고가 예상한 것과는 다르게 Update 되는 이슈가 발생한다. (Lost Update 발생)

2. 동시성 문제를 해결하기 위한 방안

의도한 대로 로직이 흘러가기 위해선 Basket의 데이터를 가져올 때 먼저 실행된 스레드가 Update 쿼리를 보낸 후 실행되게 하면 된다.

즉, 동기화를 해주면 해결되는 문제다.

  1. synchronized 키워드를 통한 동기화
    public synchronized void orderProduct(Long memberId) {
        		Member member = memberRepository.findById(memberId).orElseThrow(
                            () -> new CustomException(NOT_FOUND_MEMBER));
            List<Basket> baskets = basketQueryRepository.findBasketByMemberIdNoneLock(memberId);
            String orderNum = makeOrderNumber();
            LocalDateTime orderTime = makeOrderDataTime();
            for (Basket basket : baskets) {
                  Products products = basket.getProducts();
                  Orders orders = makeOrderByBuilder(member, orderNum, orderTime, basket, products);
                  validateStock(products, basket);
                  decreaseProductStock(products, basket.getProductQuantity());
			            updateProductStatus(basket.getProducts());
			            orderRepository.save(orders);
									productRepository.save(products);
		        }
		    }