Projects/HubEleven

[동시성 처리] DB Lock : Pessimistic Lock (비관적 락) - 실습

annovation 2026. 2. 18. 23:56

Pessimistic Lock (비관적 락)

💡Pessimistic Lock (비관적 락) 이란?

https://annovation.tistory.com/509

 

[동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 (실습중..)

동시성 문제 (Race Condition)💡해결 방법레이스 컨디션을 해결하는 방법에는 크게 3가지 관점이 있다.첫번째는 Java에서 지원하는 Synchronized 방법으로 해결하기두번째는 DB가 제공하는 Lock 을 이용하

annovation.tistory.com


동시성 처리

💡동시성 처리 전 코드

  • StockServiceImpl
@Override
@Transactional
public StockResult decreaseStock(StockRequests.Decrease request) {

    Product product = getProductOrThrow(request.productId());

    Stock stock = getStockOrThrow(request.productId());

    stock.decreaseQuantity(request.quantity());

    return StockResult.from(stock, product.getName());
}
  • Stock
public void decreaseQuantity(int decreaseQuantity) {
    validateDecreaseQuantity(decreaseQuantity);

    if (this.quantity < decreaseQuantity) {
        throw new GlobalException(INSUFFICIENT_STOCK);
    }

    this.quantity -= decreaseQuantity;
}
  • JpaStockRepository
public interface JpaStockRepository extends JpaRepository<Stock, UUID> {

    Optional<Stock> findByProductId(UUID productId);
}
  • StockRepository
public interface StockRepository {

	Stock save(Stock stock);

	Optional<Stock> findByProductId(UUID productId);
}
  • StockRepositoryAdapter
@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

	private final JpaStockRepository jpaStockRepository;

	@Override
	public Stock save(Stock stock) {
		return jpaStockRepository.save(stock);
	}

	@Override
	public Optional<Stock> findByProductId(UUID productId) {
		return jpaStockRepository.findByProductId(productId);
	}
}

 

💡동시성 처리 전 테스트 코드 결과

  • 재고 100개에 대해 1개씩 100번 동시 차감했을 때 0이 되어야 하는데, 실제로는 88이 남아 동시성 제어가 제대로 되지 않아 일부 차감이 반영되지 않은 상태에서 발생한 AssertionFailedError가 발생했다.

💡동시성 처리 한 코드

  • JpaStockRepository
public interface JpaStockRepository extends JpaRepository<Stock, UUID> {

    Optional<Stock> findByProductId(UUID productId);

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.productId = :productId")
    Optional<Stock> findByProductIdForUpdate(@Param("productId") UUID productId);
}

 

➡️ @Lock(PESSIMISTIC_WRITE)가 붙어서 실행 시점에 FOR UPDATE가 추가된다.

  • StockRepository
public interface StockRepository {

	Stock save(Stock stock);

	Optional<Stock> findByProductId(UUID productId);
	
	Optional<Stock> findByProductIdForUpdate(UUID productId);
}
  • StockRepositoryAdapter
@Repository
@RequiredArgsConstructor
public class StockRepositoryAdapter implements StockRepository {

	private final JpaStockRepository jpaStockRepository;

	@Override
	public Stock save(Stock stock) {
		return jpaStockRepository.save(stock);
	}

	@Override
	public Optional<Stock> findByProductId(UUID productId) {
		return jpaStockRepository.findByProductId(productId);
	}
	
	@Override
	public Optional<Stock> findByProductIdForUpdate(UUID productId) {
		return jpaStockRepository.findByProductIdForUpdate(productId);
	}
}
  • StockServiceImpl
private Stock getStockForUpdateOrThrow(UUID productId) {
    return stockRepository.findByProductIdForUpdate(productId)
            .orElseThrow(() -> new GlobalException(STOCK_NOT_FOUND));
}

 

➡️ Lock 조회 메서드 추가

 

💡동시성 처리 후 테스트 코드 결과

  • 테스트가 성공적으로 통과되었다 !