작성
·
31
0
안녕하세요!
강의를 복습하던 중 의문이 생겼습니다.
JPA에서 @Version을 사용하면 자동으로 낙관적락이 적용되는 걸로 알고 있는데 @Lock(OPTIMISTIC)이 필요한 이유가 궁금했습니다.
그래서 알아보니 @Version의 경우는 엔티티에 수정/삭제가 될 경우에만 version을 체크하기 때문에 읽기만 존재할 때도 다른 곳에서 변경이 됐는지 감지하기 위해서는 @Lock(OPTIMISTIC)을 사용하라고 하던데 이게 맞을가요?
혹시 맞다면 @Lock(OPTIMISTIC)을 사용해서 OptimisticLockException이 발생하는 예시코드 부탁드립니다.
아래와 같이 테스트코드를 작성해봤는데 테스트가 성공하지 않아서요
@Test
void optimistic_lock_on_read_conflict() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<Void> task1 = () -> {
service.readWithOptimisticLock(productId);
return null;
};
Callable<Void> task2 = () -> {
service.updatePrice(productId, 4000);
return null;
};
Future<Void> f1 = executor.submit(task1);
Future<Void> f2 = executor.submit(task2);
f2.get();
assertThatThrownBy(f1::get) // 읽기만 했던 쪽도 커밋 시점에서 충돌 감지
.hasCauseInstanceOf(OptimisticLockingFailureException.class);
executor.shutdown();
}
//service.readWithOptimisticLock(productId);
@Transactional(readOnly = true)
public void readWithOptimisticLock(Long id) {
productRepository.findByIdWithOptimisticLock(id);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
productRepository.flush();
}
//service.updatePrice(productId, 4000);
@Transactional
public void updatePrice(Long id, int newPrice) {
Product product = productRepository.findById(id).orElseThrow();
product.setPrice(newPrice);
productRepository.flush();
// flush 시점에 @Version 체크됨
}
답변 2
0
안_님 안녕하세요.
말씀하신것처럼 업데이트시 감지될때만 필요하다면 @Version 만 붙여도 무방할것같습니다.
강의에서 Lock 을 사용한것은 별다른 이유는 없고 현재 OPTIMISTIC Lock 실습중인것을 나타내고싶었습니다.
OptimisticLockException 이 발생하지 않는것은 self-invocation 문제로 transactional 이 정상적으로 동작하지 않는듯합니다.
readWithOptimisticLock 메소드를 외부 클래스로 구현하신 후에 그 클래스를 이용하도록 변경해보시면 될것 같아요.
그래도 안되시면 전체소스를 깃헙에 올려주신 후에 주소를 알려주시면 다운받아서 확인해보도록 하겠습니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. 안_님!
질문 주신 내용에 대해 설명해 드리겠습니다. JPA에서 @Version
을 통해 낙관적 락(Optimistic Lock)을 구현할 수 있습니다. 이것은 엔티티가 수정/삭제될 때 version을 체크하여 동시성 문제를 감지합니다. 그러나 @Lock(OPTIMISTIC)
어노테이션은 데이터 조회 시점에도 락을 잡을 수 있도록 하여, 데이터 읽기 때 발생할 수 있는 동시성 문제를 감지하고자 할 때 사용됩니다.
테스트 코드에서 OptimisticLockException
이 발생하지 않는 이유는 @Lock(OPTIMISTIC)
을 통해 읽기 시점에 락을 걸지 않았기 때문일 수 있습니다. @Lock(OPTIMISTIC)
을 사용하지 않으면 읽기 시점에는 버전 체크가 이루어지지 않기 때문에 데이터를 읽기만 하는 트랜잭션에서는 버전 불일치로 인한 예외가 발생하지 않습니다.
이를 해결하기 위해서는 productRepository.findByIdWithOptimisticLock(id);
메서드에서 @Lock(OPTIMISTIC)
을 적용하여 읽기 시점에도 락을 잡도록 설정해야 합니다. 샘플 코드에서는 읽기 트랜잭션에서 명시적으로 락을 잡는 로직이 빠져 있어, OptimisticLockException이 트리거되지 않은 것 같습니다.
먼저, @Lock(OPTIMISTIC)
을 적용한 메서드를 작성합니다.
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.OPTIMISTIC)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Product findByIdWithOptimisticLock(Long id);
}
이 인터페이스에 @Lock(OPTIMISTIC)
을 적용하면, findByIdWithOptimisticLock
메서드를 호출할 때 조회 시점에서 낙관적 락을 걸고, 이후 데이터 변경 시점에서 버전 충돌 여부를 확인합니다.
이렇게 설정 후 테스트 코드를 다시 실행했을 경우, 데이터 업데이트와 동시에 읽기가 이루어질 때 버전 충돌에 따른 OptimisticLockingFailureException
이 발생할 수 있습니다.
낙관적 락을 활용할 때는 데이터 일관성과 동시성 문제를 잘 이해하고, 적절한 트랜잭션 설계와 테스트를 통해 문제를 방지할 수 있습니다.
추가적으로 관련된 질문 및 사례를 확인하여 문제가 발생한 다른 사례와 비교해 보시기를 추천드립니다:
- 낙관적 락 테스트 실패
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.