낙관적 락은 읽기 작업이 많을 경우 성능상 이점이 있다. 데이터를 읽을 때 DB 레벨에서 락을 걸지 않기 때문이다. 이로 인해 데드락 또한 발생할 여지가 없다. 하지만 가정 자체가 트랜잭션 간 충돌이 발생하지 않는 것이기 때문에, 쓰기 작업에 대한 동시성 처리가 빈번한 경우에는 적합하지 않다.
반면에 비관적 락은 쓰기 작업이 빈번할 경우에 적합한 락 전략이다. 데이터를 읽기 위해 DB 레벨에서 락을 걸기 때문에 동시에 들어오는 요청에 대해서 안전하게 처리할 수 있다. 하지만 단순한 읽기 작업에도 레코드에 대한 락을 걸어야하기 때문에 읽기 작업이 빈번한 상황에는 적합하지 않을 수 있다.
=> 비관적 락을 사용했다. 낙관적 락은 트래내잭션의 충돌이 발생하지 않는다고 가정하에 진행하기 때문에 다소 위험 부담이 있을 수 있다고 생각했고 실제로 충돌이 나면 개발자가 일일이 롤백을 해주어야 하는 문제가 있다. 그래서 비관적 락을 사용하여 충돌이 발생하지 않더라도 일단 락을 걸고보는 방식을 택했다.
(현재는 결제까지 완료해야 재고가 줄어들지만, 결제기능 구현 전 주문하기 버튼 누르면 재고 감소하는 로직)
데드락(교착상태)란 두개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 가리킨다. 한정된 자원을 여러 곳에서 사용하려고 할 때 발생할 수 있다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT i FROM Item i WHERE i.no = :no")
Optional<Item> findByIdWithLock(@Param("no") Long no);
@Transactional
public Long order(OrderItemRequestDto orderItemDto, Member member) {
// 선택한 상품 주문
Item item = itemRepository.findByIdWithLock(orderItemDto.getItemNo()).orElseThrow(
() -> new BusinessException(ErrorCode.NOT_FOUND_ITEM)
);
// 이전 주문(결제하지 않은, 주문 상태 ORDER) 이 있는지 확인
Order existOrder = getStatusOrder(member);
if (existOrder != null) { // 이전 주문이 있으면 삭제
orderRepository.delete(existOrder);
}
List<OrderItem> orderItemList = new ArrayList<>(); // 주문 상품 담는 리스트
orderItemList.add(orderItemDto.toEntity(item)); // (상품 담아) 주문 상품 생성
Order order = Order.toEntity(member, orderItemList, false); // (주문 상품 담아) 주문 생성
orderRepository.save(order); // 주문 저장
return order.getNo();
}
- 미결제 주문 존재 시 삭제
- 새로운 주문 생성 및 저장
curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3MjMyLCJleHAiOjE3MjQxNTkwMzIsInN1YiI6ImVzYzAyMTgiLCJubyI6MSwicm9sZSI6IlJPTEVfQURNSU4ifQ._Z8cAc5RCVkRwLb-WfD_K0pb0sVjQMffFB490iz-TIE' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3MzE2LCJleHAiOjE3MjQxNTkxMTYsInN1YiI6ImVzYzAyMTkiLCJubyI6Miwicm9sZSI6IlJPTEVfTUVNQkVSIn0.zdpa5aQyWEC43J_cr0j2AnIPpyhOxiijAxxQX49wKzQ' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3MzYyLCJleHAiOjE3MjQxNTkxNjIsInN1YiI6ImVzYzAyMjAiLCJubyI6Mywicm9sZSI6IlJPTEVfTUVNQkVSIn0.U-Xx5ABfIG74Vrym_PsjNR_ru2wA7lSiFYdsmPp-iMs' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3Mzg0LCJleHAiOjE3MjQxNTkxODQsInN1YiI6ImVzYzAyMjEiLCJubyI6NCwicm9sZSI6IlJPTEVfTUVNQkVSIn0.qbquriyQMaoR1rl6aOUKBu0V1yotThgngwJy-GqlIKk' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDAxLCJleHAiOjE3MjQxNTkyMDEsInN1YiI6ImVzYzAyMjIiLCJubyI6NSwicm9sZSI6IlJPTEVfTUVNQkVSIn0.dgaPi_dWpT4bqdaGSdr5PwgGxWLTaNW8QzaSzJrNPmE' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDE3LCJleHAiOjE3MjQxNTkyMTcsInN1YiI6ImVzYzAyMjMiLCJubyI6Niwicm9sZSI6IlJPTEVfTUVNQkVSIn0.f08EfENXd4-Dw731I9ENCngnopGOSylUdPSAdtydtmw' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDMzLCJleHAiOjE3MjQxNTkyMzMsInN1YiI6ImVzYzAyMjQiLCJubyI6Nywicm9sZSI6IlJPTEVfTUVNQkVSIn0.tismQlWqGSwTryA_HYfbmgFzAaHiPoZt9nzSt0M0pC8' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDQ4LCJleHAiOjE3MjQxNTkyNDgsInN1YiI6ImVzYzAyMjUiLCJubyI6OCwicm9sZSI6IlJPTEVfTUVNQkVSIn0._6K_OC0fmsqmUZE3IphfrGpCaVx2ZS_kkWVa29a-1so' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDcxLCJleHAiOjE3MjQxNTkyNzEsInN1YiI6ImVzYzAyMjYiLCJubyI6OSwicm9sZSI6IlJPTEVfTUVNQkVSIn0.4QVbVjSKw_qWa38rcOgcWm4ivZffh2sHQPoZQIMFk3I' \
--data '{
"itemNo" : 1,
"count" : 1
}'&curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--header 'Cookie: Authorization=Bearer%20eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIke0pXVF9JU1NVRVJ9IiwiaWF0IjoxNzI0MTU3NDg1LCJleHAiOjE3MjQxNTkyODUsInN1YiI6ImVzYzAyMjciLCJubyI6MTAsInJvbGUiOiJST0xFX01FTUJFUiJ9.rqdMfPF8JrwhtB53HMnSUHzGBbfN87KZLyuGZYcbrT4' \
--data '{
"itemNo" : 1,
"count" : 1
}'
주문상품 테이블
item_no : 1(복숭아)
주문 테이블