진행 환경
💡진행 환경
- Java 17
- Spring Boot 3.5.7
- MySQL 8.0.44
- Gradle
문제 상황
💡 문제 상황
- Pessimistic Lock (낙관적 락) 적용을 위해 @Lock(LockModeType.PESSIMISTIC_WRITE) 추가 후 아래와 같은 오류 발생

➡️ TransactionRequiredException: Query requires transaction be in progress
💡 문제 증상
- PESSIMISTIC_WRITE 락 쿼리를 트랜잭션 없이 실행해서 난 오류
원인 분석
💡 원인 1 : 테스트 코드에서 단순히 재고 데이터를 읽어오는 작업이더라도 트랜잭션이 걸려있어야 한다.
- 현재 테스트 코드에서 레이스 컨디션 검증 포인트는 아래 stockServiceImpl 에서 decreaseStock 을 통해 동시 호출 하는 구간이다.
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
stockServiceImpl.decreaseStock(
StockFixture.decreaseRequest(product, 1)
);
} finally {
countDownLatch.countDown();
}
});
}
- 그런데, JpaStockRepository.findByProductId에 @Lock(PESSIMISTIC_WRITE) 을 통해 동시성 처리를 하고 있기 때문에 findByProductId 와 같은 단순 조회 로직이더라도 SELECT가 아니라 락 쿼리(FOR UPDATE)로 실행되고 이는 트랜잭션 환경이 필수적이다.
public interface JpaStockRepository extends JpaRepository<Stock, UUID> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Stock> findByProductId(UUID productId);
}
- 현재 StockServiceImpl 메서드들은 트랜잭션이 걸려있지만, 테스트 코드에서는 StockServiceImpl 을 거치지 않고 테스트가 Repository를 직접 호출 했기 때문에 해당 호출 자체에는 트랜잭션이 없다.
Stock updatedStock = jpaStockRepository
.findByProductId(product.getProductId())
.orElseThrow();
assertEquals(0, updatedStock.getQuantity());
해결 방안 검토
💡 해결 방안 1 : Lock 없는 메서드로 읽기
✅ 새롭게 발견한 문제점 : 단순 조회 API 까지 Lock 조회가 되어 성능이 떨어진다.
@Override
@Transactional(readOnly = true)
public StockResult getStockByProductId(UUID productId) {
Product product = getProductOrThrow(productId);
Stock stock = getStockOrThrow(productId);
return StockResult.from(stock, product.getName());
}
- 따라서 조회용으로 Lock 이 없는 메서드를 따로 분리하여 설계한다.
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);
}
결과
💡 테스트 코드 정상적으로 통과!

'Projects > HubEleven' 카테고리의 다른 글
| [동시성 처리] DB Lock : Optimistic Lock (낙관적 락) - 실습 (0) | 2026.02.20 |
|---|---|
| [동시성 처리] DB Lock : Pessimistic Lock (비관적 락) - 실습 (0) | 2026.02.18 |
| [동시성 처리] Trouble Shooting : 멀티스레드 재고 감소 통합 테스트 코드 Eureka Server Connection refused (0) | 2026.02.16 |
| [동시성 처리] synchronized vs Reentrant Lock - 실습 (0) | 2026.02.15 |
| [동시성 처리] JVM Lock : synchronized VS Reentrant Lock (실습중..) (0) | 2026.02.14 |