📂Stock.java
@Entity
@Getter
@NoArgsConstructor
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long quantity;
public Stock(Long productId, Long quantity) {
this.productId = productId;
this.quantity = quantity;
}
// 재고 수량 감소 메서드
public void decreaseQuantity(Long quantity) {
if (this.quantity - quantity < 0) {
throw new RuntimeException("재고는 0개 미만이 될 수 없습니다.");
}
this.quantity -= quantity;
}
}
📂StockService.java
@Service
@Transactional
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
public void decreaseStock(Long id, Long quantity) {
// Stock을 조회
// 재고 감소
// 갱신된 값을 저장
Stock stock = stockRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 id입니다."));
stock.decreaseQuantity(quantity);
stockRepository.saveAndFlush(stock);
}
}
📂StockServiceTest.java
@SpringBootTest
class StockServiceTest {
@Autowired
private StockService stockService;
@Autowired
private StockRepository stockRepository;
// 테스트 전 상품 재고 입력
@BeforeEach
public void beforeTest() {
stockRepository.saveAndFlush(new Stock(1L, 100L));
}
// 테스트 후 모든 재고 삭제
@AfterEach
public void afterTest() {
stockRepository.deleteAll();
}
@Test
@DisplayName("재고 감소 로직 테스트")
public void decreaseStockTest(){
// when
stockService.decreaseStock(1L, 1L);
// then
Stock stock = stockRepository.findById(1L)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 id"));
assertAll(
() -> assertNotNull(stock),
() -> assertEquals(99, stock.getQuantity())
);
}
}
save()
saveAndFlush()
saveAndFlush()
를 불필요하게 사용하면 성능 저하(DB I/O 증가)Race Condition
Race Condition
은 여러 개의 프로세스 또는 스레드가 공유 자원에 동시 접근(변경) 할 때Thread1 | Stock | Thread2 |
---|---|---|
SELECT * FROM stock WHERE id = 1 | {id: 1, quantity: 5} | |
UPDATE SET quantity = 4 FROM stock WHERE id = 1 | {id: 1, quantity: 4} | |
{id: 1, quantity: 4} | SELECT * FROM stock WHERE id = 1 | |
{id: 1, quantity: 3} | UPDATE SET quantity = 4 FROM stock WHERE id = 1 |
Thread1
이 데이터를 가져서 갱신하기 전에 Thread2
가 데이터를 가져가서 갱신Thread1
과 Thread2
가 모두 갱신을 하지만,Thread1 | Stock | Thread2 |
---|---|---|
SELECT * FROM stock WHERE id = 1 | {id: 1, quantity: 5} | |
{id: 1, quantity: 5} | SELECT * FROM stock WHERE id = 1 | |
UPDATE SET quantity = 4 FROM stock WHERE id = 1 | {id: 1, quantity: 4} | |
{id: 1, quantity: 4} | UPDATE SET quantity = 4 FROM stock WHERE id = 1 |