publicinterfaceStockRepositoryextendsJpaRepository<Stock,Long>{// PESSIMISTIC_WRITE: SELECT FOR UPDATE — 읽기/쓰기 모두 차단@Lock(LockModeType.PESSIMISTIC_WRITE)@Query("SELECT s FROM Stock s WHERE s.productId = :productId")Optional<Stock>findByProductIdWithLock(@Param("productId")Long productId);// PESSIMISTIC_READ: SELECT FOR SHARE — 쓰기만 차단, 읽기는 허용@Lock(LockModeType.PESSIMISTIC_READ)Optional<Stock>findById(Long id);}
@Service@RequiredArgsConstructorpublicclassStockService{privatefinalStockRepository stockRepository;@Transactionalpublicvoiddecrease(Long productId,int quantity){// 비관적 락으로 조회 → 다른 트랜잭션은 이 Row에 접근 불가Stock stock = stockRepository.findByProductIdWithLock(productId).orElseThrow(()->newResourceNotFoundException("Stock not found"));if(stock.getQuantity()< quantity){thrownewBusinessException("재고 부족");} stock.decrease(quantity);// 재고 감소// 트랜잭션 종료 시 락 해제}}
// 락 대기 타임아웃 설정 (밀리초)publicinterfaceStockRepositoryextendsJpaRepository<Stock,Long>{@Lock(LockModeType.PESSIMISTIC_WRITE)@QueryHints(@QueryHint(name ="javax.persistence.lock.timeout", value ="3000"))Optional<Stock>findByProductIdWithLock(@Param("productId")Long productId);}