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();
}
}
@Transactional
어노테이션을 붙이게 되면 동시성 문제가 발생할 수 있기 때문에 붙이지 않았다.
@Transactional
은 메소드의 시작 전에 시작되고 메소드가 끝난 후에 커밋된다.@Transactional
을 붙이지 않게 되면서 문제가 발생하게 되는데, 수량이 변경된 제품을 save()
하는 과정에서 SELECT
쿼리가 한번 더 발생하게 됐다.우선 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
쿼리를 발생 시킨후 UPDATE
나 INSERT
를 실행하게 된다.
@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
을 사용하면 동시성 제어가 안 되고, 사용하지 않으면 성능에서 손해를 보는 상황이다.