저번 시간에 이어 계속 구현해보았다.
이번 요구사항에는 상품에 재고(stock) 정보를 추가하고, 주문 생성 시 재고가 차감되도록 구현하는 것이 포함되어 있었다.
먼저 Product 엔티티에 stock 필드와 차감 메서드를 추가했다.
주문 생성 시 상품을 조회한 뒤 product의 decreaseStock()을 호출하는 방식으로 재고를 차감했다. 재고가 0일 때는 예외를 던져 주문이 생성되지 않도록 처리했다.
기본 구현은 완성했지만 한 가지 문제가 남아있었다. 동시에 여러 요청이 들어올 경우 재고가 정확히 차감되지 않을 수 있다는 점이다.
일반적인 재고 차감 흐름은 이렇다.
1. SELECT stock FROM product WHERE id = 1 → stock = 10
2. stock - 1 = 9
3. UPDATE product SET stock = 9 WHERE id = 1
1번과 3번 사이에 다른 요청이 끼어들면 문제가 생긴다.
A: 조회 → stock = 10
B: 조회 → stock = 10 ← A가 아직 커밋 안 했으니 똑같이 10 읽음
A: stock = 9로 업데이트
B: stock = 9로 업데이트 ← 2번 차감됐는데 9가 됨
대표적으로 낙관적 락과 비관적 락이 있다.
낙관적 락은 충돌이 없을 거라고 가정하고 진행한다.
트랜잭션 A 시작 (version=1)
트랜잭션 B 시작 (version=1)
A가 재고 차감 후 커밋 → version=2
B가 커밋 시도 → version이 달라! → 예외 발생
→ B는 재시도 해야 함
DB 락을 걸지 않고 @Version 필드로 버전을 관리하며, 커밋 시점에 버전이 다르면 충돌로 판단해 예외를 발생시킨다. 충돌이 적은 환경에서 성능이 좋지만, 충돌 발생 시 재시도 로직을 직접 구현해야 한다.
비관적 락은 충돌이 날 거라고 가정하고 조회 시점에 DB 행을 잠근다.
트랜잭션 A 시작 → 락 획득
트랜잭션 B 시작 → 락 대기
A가 재고 차감 후 커밋 → 락 해제
B가 락 획득 → 차감 진행
다른 트랜잭션은 락이 해제될 때까지 대기하므로 순서가 보장된다. 구현이 단순하지만 대기가 발생해 성능이 떨어질 수 있다.
| 낙관적 락 | 비관적 락 | |
|---|---|---|
| 충돌 감지 | 커밋 시점 | 조회 시점 |
| 충돌 많을 때 | 재시도 반복 → 비효율 | 대기 후 순서 처리 → 안정적 |
| 충돌 적을 때 | 빠름 | 불필요한 대기 발생 |
| 구현 복잡도 | 재시도 로직 필요 | 단순 |
각각 언제 쓰면 좋을까?
낙관적 락
비관적 락
재고 차감은 동시 요청이 많이 몰릴 수 있어 비관적 락이 적합하다고 생각했다. 그런데 찾아보다가 원자적 UPDATE라는 방식을 알게 됐다.
락 없이 쿼리 자체에서 조회와 차감을 한 번에 처리하는 방식이다.
UPDATE product SET stock = stock - 1 WHERE id = 1 AND stock > 0
이런식으로 업데이트 쿼리에서 재고 차감을 해버림.
DB가 이 과정을 한 번에 처리하기 때문에 끼어들 틈이 없다. 재고가 부족하면 AND stock >= quantity 조건을 만족하지 못해 영향받은 행이 0개가 되고, 반환값으로 성공/실패를 구분한다.

ㄴ> repository에 추가해줌.
int updated = productRepository.decreaseStock(productId, quantity);
if (updated == 0) {
throw new IllegalStateException("재고가 부족합니다.");
}
락을 걸지 않아 성능이 좋고, 쿼리 한 번으로 차감과 재고 확인이 동시에 되어 세 가지 방법 중 가장 단순하고 효율적이라고 판단해 이 방식을 선택했다.
이번 프로젝트에서 Java 21을 선택했다.
사실 거창한 이유는 없고, 저번 프로젝트에서 썼던 버전이라 그냥 이어서 썼다.
근데 저번 게시글 댓글에서 자바 버전 차이와 사용 이유를 알고 쓰는게 좋겠다는 피드백을 받고 버전 선택 이유에 대해 한번 정리해봤다.
| 버전 | 주요 추가 기능 |
|---|---|
| Java 8 | Lambda, Stream API, Optional, 새로운 날짜/시간 API |
| Java 11 | HttpClient API 표준화, var 키워드 |
| Java 17 | Sealed Class, Record, Pattern Matching, Text Block 정식 출시 |
| Java 21 | Virtual Thread 정식 출시, Record Pattern, Pattern Matching for Switch 정식 출시 |
| Java 25 | Stream Gatherers, Scoped Values, AOT Class Loading, Compact Object Headers, Flexible Constructor Bodies 정식 출시 |
Java 21 주요 추가 기능
Java 21의 핵심은 단연 가상 스레드였고, 나머지 기능들은 코드를 더 간결하게 쓸 수 있도록 언어 표현력을 높이는 방향이었다.
현재 기준 최신 LTS는 2025년 9월에 출시된 Java 25다.
자바 버전은 정말 금방금방 나오는 것 같다..
Java 25 주요 추가 기능
이번 프로젝트에서 가상 스레드를 포함해 Java 21에서 새로 추가된 기능들을 직접 쓰진 않았다. 사실 가상 스레드 말고는 뭐가 추가됐는지 까먹었는데, 이번 기회에 정리해보니 좋았다.
다음 프로젝트에서는 이번에 알게 된 기능들도 한번 써봐야겠다.
Java 25도 꽤 많은 게 추가됐는데 언젠가 버전을 올려볼 기회가 생기면 같이 살펴봐야겠다.
단순 메시지 반환을 넘어 클라이언트가 "왜 실패했는지"를 코드로 식별할 수 있게 하는 것이 중요하다는 피드백을 받고 에러처리를 추가로 구현하였다.
현재 문제점
개선 결과

오류 메세지도 깔꼼해졌다 👍