낙관적 Lock 을 활용하는 방법이다. 이 방법은 데이터를 조회할 때 가져온 해당 데이터의 version 과 수정할 때 확인한 데이터의 version 을 비교하여 version 이 다를 경우, 조회와 수정 로직을 어플리케이션 레벨에서 다시 수행하는 것이다.
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long quantity;
@Version ## @Version 어노테이션 추가
private Long version;
}
@Lock(LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.productId = :productId")
public Stock findStockByProductIdWithOptimisticLock(Long productId);
@Service
public class OptimisticStockService {
private final StockRepository stockRepository;
public OptimisticStockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
@Transactional
public void decrease(Long productId , Long quantity){
Stock stock = this.stockRepository.findStockByProductIdWithOptimisticLock(productId);
log.info("Current quantity = {}" , stock.getQuantity());
stock.decrease(quantity);
this.stockRepository.save(stock);
}
}
public class OptimisticLockStockFacade {
private final OptimisticStockService optimisticStockService;
public OptimisticLockStockFacade(OptimisticStockService stockService) {
this.optimisticStockService = stockService;
}
public void decrease(Long id , Long quantity) throws InterruptedException {
while (true){
try{
this.optimisticStockService.decrease(id , quantity);
break;
}catch (Exception e){
Thread.sleep(50);
}
}
}
}
그리고 OptimisticStockService 와 이를 호출하는 OptimisticStockServiceFacade 를 만들어 Facade 에서 OptimisticStockService 를 호출하는 방식으로 이를 이용할 수 있다.
여기서 의문이 생긴 지점이 있었다. 왜 Facade 를 이용할까? 그냥 Service 에서 직접 While 을 사용하여 lock 의 version 이 맞을 때 까지 재시도 할 수 있지 않을까?
그래서 직접 코드를 작성해보았다.
@Transactional
public void decreaseStockWithOptimisticLock(Long productId , Long quantity){
Stock stock;
while(true){
try{
stock = this.stockRepository.findStockByProductIdWithOptimisticLock(productId);
log.info("Current quantity = {}" , stock.getQuantity());
stock.decrease(quantity);
this.stockRepository.save(stock);
break;
}catch (Exception e){
System.out.println("e = " + e);
log.info("Retrying OptimisticLock Decreasing");
}
}
}

그러면 save 말고 saveAndFlush 를 사용하면 어떨까? 이 메서드는 entity 를 영속성 컨텍스트에 저장하고 이를 db 에 flush 작업까지 수행해준다.
@Transactional
public void decreaseStockWithOptimisticLock(Long productId , Long quantity){
Stock stock;
while(true){
try{
stock = this.stockRepository.findStockByProductIdWithOptimisticLock(productId);
log.info("Current quantity = {}" , stock.getQuantity());
stock.decrease(quantity);
this.stockRepository.saveAndFlush(stock);
break;
}catch (Exception e){
System.out.println("e = " + e);
log.info("Retrying OptimisticLock Decreasing");
}
}
}

마지막으로 그러면 @Transactional 어노테이션을 없애면 어떨까? 그러면 영속성 컨텍스트의 값이 아닌 DB 의 값에 직접 접근하니까 가능하지 않을까?

