문제가 발생한 부분

public void orderProductByRedissonLock(Long memberId) {
        RLock lock = redissonClient.getLock("orderLock");

        try {
            boolean available = lock.tryLock(300, 300, TimeUnit.SECONDS);

            if (available) {
                    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(products);
                        basketRepository.delete(basket);
                        orderRepository.save(orders);
                        productRepository.save(products);
                    }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

문제가 발생한 원인

우선 save() 메소드의 내부를 확인해보도록 하자

@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

save() 메소드는 매개인자로 받은 엔티티가 존재하지 않으면 저장하고 존재하면 수정하게 된다.

즉, 존재하는지 존재하지 않는지를 알기 위해서 SELECT 쿼리를 발생 시킨후 UPDATEINSERT 를 실행하게 된다.

@Override
	@Transactional
	@SuppressWarnings("unchecked")
	public void delete(T entity) {

		Assert.notNull(entity, "Entity must not be null!");

		if (entityInformation.isNew(entity)) {
			return;
		}

		Class<?> type = ProxyUtils.getUserClass(entity);

		T existing = (T) em.find(type, entityInformation.getId(entity));

		// if the entity to be deleted doesn't exist, delete is a NOOP
		if (existing == null) {
			return;
		}

		em.remove(em.contains(entity) ? entity : em.merge(entity));
	}

delete() 메소드도 비슷한 로직으로 동작하기 때문에 SELECT 쿼리 후 DELETE쿼리가 발생하게 된다.

@Transactional 을 사용하면 동시성 제어가 안 되고, 사용하지 않으면 성능에서 손해를 보는 상황이다.