[최종프로젝트]쿼리 개선<2>

주준호·2024년 5월 2일

개발공부일지

목록 보기
5/5
post-thumbnail
  • OrderService의 createOrder()메서드 쿼리 개선

  • createOrder
    @Transactional
    public void createOrder(
        Map<Long, Long> basket,
        UserDetailsImpl userDetails,
        Long addressId
    ) {
        String lockKey = "order_lock";
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!isLocked) {
                throw new RuntimeException("락 획득에 실패했습니다.");
            }
            System.out.println(userDetails.getUser().getId() + "번 유저가 주문을 합니다.");
            // 주문 객체 생성
            Order order = new Order(userDetails.getUser().getId(), OrderState.NOTPAYED, addressId);
            // 상품 수량 검증 코드
            checkBasket(basket, order);
            // 주문 객체 저장
            orderRepository.save(order);

            // 장바구니를 순회하며
            // 주문한 상품과 상품 개수를 상품 상세정보 테이블에 업데이트
            // 상품 테이블의 상품 수량 업데이트
            for (Map.Entry<Long, Long> entry : basket.entrySet()) {
                Long productId = entry.getKey();
                Long quantity = entry.getValue();
                // 상태를 업데이트하는 메서드
                updateStockAndCreateOrderDetail(productId, quantity, order);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("락 획득 중 오류가 발생했습니다.", e);
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }
  • checkBasket
    public void checkBasket(Map<Long, Long> basket, Order order) {
        for (Map.Entry<Long, Long> entry : basket.entrySet()) {
            Long productId = entry.getKey();
            Long quantity = entry.getValue();
            User user = addressFeignClient.getUser(order.getUserId());
            String email = user.getEmail();// 주문한 사용자의 이메일 주소 가져오기
            String orderDetails = "Order ID: " + order.getId(); // 주문 상세 내용
            //레디스로
            Long stock = productFeignClient.getProduct(productId).getStock();
            if (stock == 0) {
                System.out.println("재고부족");
                emailService.sendCancellationEmail(email, orderDetails,
                    EmailType.STOCK_OUT); // 취소 이메일 발송
                emailService.saveStock_Out_UserInfoToRedis(email, productId);
                throw new BadRequestException("상품 ID: " + productId + ", 재고가 없습니다.");
            }
            if (stock < quantity) {
                System.out.println("재고부족2");
                emailService.sendCancellationEmail(email, orderDetails,
                    EmailType.STOCK_OUT); // 취소 이메일 발송
                emailService.saveStock_Out_UserInfoToRedis(email, productId);
                throw new BadRequestException(
                    "상품 ID: " + productId + ", 재고가 부족합니다. 요청 수량: " + quantity + ", 현재 재고: "
                        + stock);
            }
        }
    }
  • updateStockAndCreateOrderDetail
  @Transactional
    public void updateStockAndCreateOrderDetail(Long productId, Long quantity, Order order) {
        //영속성 컨텍스트를 초기화
        entityManager.clear();
        //상품 객체 생성
        Product product = productFeignClient.getProduct(productId);
        //상품 수량 수정
        product.updateStockAfterOrder(quantity);
        System.out.println("현재 상품 수량: " + product.getStock());
        //수정된 상품 저장
        productFeignClient.save(product);
        //상품 상세정보 객체 저장
        OrderDetail orderDetail = new OrderDetail(order.getId(), productId, quantity, product.getPrice(), product.getName());
        orderDetailRepository.save(orderDetail);
    }

10개의 상품을 장바구니에 담고 주문 생성 하는 상황에서 실행 시간을 체크해 보았다.

  • 개선 전

  • createOrder
@Transactional
    public void createOrder(
        Map<Long, Long> basket,
        UserDetailsImpl userDetails,
        Long addressId
    ) {
        String lockKey = "order_lock";
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!isLocked) {
                throw new RuntimeException("락 획득에 실패했습니다.");
            }
            System.out.println(userDetails.getUser().getId()+"번 유저가 주문을 합니다.");
            // 주문 객체 생성
            Order order = new Order(userDetails.getUser().getId(), OrderState.NOTPAYED, addressId);
            // 상품 수량 검증 코드
            checkBasket(basket, order);
            // 주문 객체 저장
            Order savedOrder = orderRepository.save(order);

            // 장바구니를 순회하며
            // 주문한 상품과 상품 개수를 상품 상세정보 테이블에 업데이트
            // 상품 테이블의 상품 수량 업데이트
            updateStockAndCreateOrderDetail(basket, savedOrder);

        } catch (InterruptedException e) {
            throw new RuntimeException("락 획득 중 오류가 발생했습니다.", e);
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }
  • checkBasket
public void checkBasket(Map<Long, Long> basket, Order order) {
        List<Long> productIdList = new ArrayList<>();
        for (Map.Entry<Long, Long> entry : basket.entrySet()) {
            Long productId = entry.getKey();
            productIdList.add(productId);
        }

        String orderDetails = "Order ID: " + order.getId(); // 주문 상세 내용
        //레디스로
        List<Product> productList = productFeignClient.getProductList(productIdList);

        for(Product product : productList){
            Long stock = product.getStock();

            if (stock < basket.get(product.getId())) {
                User user = addressFeignClient.getUser(order.getUserId());
                String email = user.getEmail();// 주문한 사용자의 이메일 주소 가져오기
                System.out.println("재고부족2");
                emailService.sendCancellationEmail(email, orderDetails,
                    EmailType.STOCK_OUT); // 취소 이메일 발송
                emailService.saveStock_Out_UserInfoToRedis(email, product.getId());
                throw new BadRequestException(
                    "상품 ID: " + product.getId() + ", 재고가 부족합니다. 요청 수량: " + basket.get(product.getId()) + ", 현재 재고: "
                        + stock);
            }
        }
    }
  • updateStockAndCreateOrderDetail
@Transactional
    public void updateStockAndCreateOrderDetail(Map<Long, Long> basket, Order order) {
        //영속성 컨텍스트를 초기화
        entityManager.clear();

        List<Long> productIdList = new ArrayList<>();
        List<Long> quantityList = new ArrayList<>();
        for (Map.Entry<Long, Long> entry : basket.entrySet()) {
            Long productId = entry.getKey();
            Long quantity = entry.getValue();
            productIdList.add(productId);
            quantityList.add(quantity);
        }

        //상품 객체 생성
        List<Product> productList = productFeignClient.getProductList(productIdList);

        //상품 수량 수정
        for(Product product : productList){
            product.updateStockAfterOrder(basket.get(product.getId()));
            System.out.println("현재 상품 수량: " + product.getStock());
        }

        //수정된 상품 저장
        productFeignClient.updateBulk(productList);

        //상품 상세정보 객체 저장
        List<OrderDetail> orderDetail = new ArrayList<>();
        for(Product product : productList){
            orderDetail.add(new OrderDetail(order.getId(), product.getId(), basket.get(product.getId()), product.getPrice(), product.getName()));
        }
        orderDetailBulkRepository.saveBulk(orderDetail);
    }

동일 조건의 상황에서 쿼리 개선 후 실행 시간 체크.

개선 후

10건 주문 실행 시간 비교

  • 개선 전 : 258ms
  • 개선 후 : 48ms

🌟 약 81.3%의 성능 개선

  • OrderService의 getOrderList()메서드 쿼리 개선

  • getOrderList
public List<OrderResponseDto> getOrderList(
        UserDetailsImpl userDetails
    ) {
        List<Order> orderList = orderRepository.findOrdersByUserId(userDetails.getUser().getId());
        List<OrderResponseDto> ResponseList = new ArrayList<>();
        for (Order order : orderList) {
            Address address = addressFeignClient.findOne(order.getAddressId());
            OrderResponseDto orderResponseDto = new OrderResponseDto(order, address);
            ResponseList.add(orderResponseDto);
        }
        return ResponseList;
    }

약 190건의 주문 내역을 조회할 때 실행 시간 체크.

개선 전


  • getOrderList
public List<OrderResponseDto> getOrderList(
        UserDetailsImpl userDetails
    ) {
        List<Order> orderList = orderRepository.findOrdersByUserId(userDetails.getUser().getId());
        List<OrderResponseDto> ResponseList = new ArrayList<>();

        Set<Long> addressIdSet = new HashSet<>();
        for(Order order : orderList){
            addressIdSet.add(order.getAddressId());
        }

        Map<Order, Address> orderAddressMap = new LinkedHashMap<>();
        List<Address> addressList = addressFeignClient.findAddressListByAddressIdList(addressIdSet);

        for(Order order : orderList){
            for(Address address : addressList){
                if(address.getId().equals(order.getAddressId())){
                    orderAddressMap.put(order, address);
                }
            }
        }

        for(Map.Entry<Order, Address> entry : orderAddressMap.entrySet()){
            OrderResponseDto orderResponseDto = new OrderResponseDto(entry.getKey(), entry.getValue());
            ResponseList.add(orderResponseDto);
        }
        return ResponseList;
    }

동일 조건에서 쿼리 개선 후 실행 시간 체크.

개선 후

주문 내역 190건 조회 시간 비교

  • 개선 전 : 610ms
  • 개선 후 : 17ms

🌟 약 97.1%의 성능 개선

profile
끊임없이 배우는 백엔드 개발자입니다.

0개의 댓글