JPA ‐ Pessimistic Lock으로 동시성 이슈 해결하기 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 Pessimistic Lock 소스 코드(비관적 락)
@Service
@RequiredArgsConstructor
public class PessimisticLockStockService {
private final StockRepository stockRepository;
// 재고 감소 로직
/*
* 비관적 락 적용
* 하나의 쓰레드가 락을 획득하고 작업을 수행하는 동안 다른 쓰레드는 락을 획득하려고 시도하나 락을 획득하지 못하고 대기 상태가 된다.
*/
@Transactional
public void decrease(Long productId, Long quantity) {
Stock stock = stockRepository.findByIdWithPessimisticLock(productId);
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(Long id);
}
@Test
@DisplayName("동시에 여러 요청을 통한 상품 주문시 요청만큼의 재고 감소가 이루어진다. - 비관적 락 도입")
void 동시에_여러_요청을_통한_상품_주문시_요청만큼의_재고_감소가_이루어진다() throws InterruptedException {
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
/*
* 비관적 락을 사용하는 경우 -> @Lock(LockModeType.PESSIMISTIC_WRITE)
*/
assertThat(stock.getQuantity()).isEqualTo(0L);
}
📚 JPA 비관적 락
- JPA가 제공하는 비관적 락은 데이터베이스 트랜잭션 락 메커니즘에 의존하는 방법이다.
- 주로 SQL 쿼리에
select for update
구문을 사용하면서 시작하고 버전 정보는 사용하지 않는다. - 비관적 락은 주로
PESSIMISTIC_WRITE
락 모드를 사용한다. - 비관적 락은 다음과 같은 특징이 있다.
- 엔티티가 아닌 스칼라 타입을 조회할 때도 사용할 수 있다.
- 데이터를 수정하는 즉시 트랜잭션 충돌을 감지할 수 있다.
- 비관적 락에서 발생하는 예외는 다음과 같다.
- javax.persistence.PessimisticLockException(JPA 예외)
- org.springframework.dao.PessimisticLockingFailureException(스프링 예외 추상화)
[ PESSIMISTIC_WRITE ]
- 비관적 락이라 하면 일반적으로 이 옵션을 뜻한다.
- 데이터베이스에 쓰기 락을 걸 때 사용한다.
- 용도 : 데이터베이스에 쓰기 락을 건다.
- 동작 : 데이터베이스 select for update문을 사용해서 락을 건다.
- 이점 : NON_REPEATABLE_READ를 방지한다. 락이 걸린 로우는 다른 트랜잭션이 수정할 수 없다.
[ PESSIMISTIC_READ ]
- 데이터를 반복 읽기만 하고 수정하지 않는 용도로 락을 걸 때 사용한다.
- 일반적으로 잘 사용하지 않는다.
- 데이터베이스 대부분은 방언에 의해 PESSMISTIC_WRITE으로 동작한다.
[ PESSIMISTIC_FORCE_INCREMENT ]
- 비관적 락 중에서 유일하게 버전 정보를 사용한다.
- 비관적 락이지만 버전 정보를 강제로 증가시킨다.
- 하이버네이트는 nowait를 지원하는 데이터베이스에 대해서 for update nowait 옵션을 적용한다.
❗비관적 락과 타임아웃
비관적 락을 사용하면 락을 획득할 때까지 트랜잭션이 대기한다. 무한정 기다릴 수 없으므로 타임아웃 시간을 줄 수 있다.