작성
·
725
1
안녕하세요. 항상 강의 잘 듣고 있습니다.
낙관적 락에 대해서 직접 테스트 코드를 통해 동작 방식을 살펴보고자 하는데 생각대로 동작하지 않아서 질문남깁니다.
우선 테스트 코드 시 스프링 IOC 컨테이너를 사용하기 위해 @SpringBootTest를 선언한 상황이고, 아래는 테스트 코드 내용입니다.
스레드 1(트랜잭션1)에서 낙관적 락을 사용한 조회를 하도록 했고 커밋 되기 전, 스레드 2(르랜잭션2)에서 해당 데이터를 변경하도록 했습니다. 예상 대로라면 예외가 발생해야되는 데 정상 종료가 되어서 질문드립니다. ㅠㅠ
로그를 찍어보았는데, 예상대로 각 스레드 별로 독립적인 트랜잭션이 실행되고, 낙관적 락을 통해 마지막에 version 확인 쿼리까지 발행하는데 왜 오류가 발생하지 않는지 궁금합니다. 잘못된 점이 있으면 알려주시면 감사하겠습니다!!
답변 3
1
안녕하세요. rnqhstlr2297님
보내주신 로그와 코드를 확인해보니 쓰레드2가 먼저 수행되었습니다^^;
이 테스트가 정상 수행 되려면 쓰레드1이 다음 로직을 먼저 수행하고,
memberLockService.findMemberByOPTIMISTIC(memberId);
그 다음에 바로 쓰레드2가 다음 로직을 수행해야 하는데요.
memberLockService.findMemberAndUpdate(memberId, changeMoney);
다음과 같이 sleep 코드를 넣어서 쓰레드2의 실행을 잠시 미루면 쓰레드1이 먼저 수행되기 때문에 로그에서 오류 메시지를 확인하실 수 있을거에요.
//스래드 2(트랜잭션2) -> 데이터 수정
executorService.execute(() -> {
// === 추가 ===
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
memberLockService.findMemberAndUpdate(memberId, changeMoney);
countDownLatch.countDown();
});
오류 메시지
org.springframework.orm.ObjectOptimisticLockingFailureException: Newer version [2] of entity [[hello.jdbc.lock.MemberLock#memberA]] found in database
감사합니다.
계속 귀찮게 해서 죄송합니다..
영한님이 알려준 코드로 실행해보았는데, 스레드 1이 먼저 시작된 것을 로그로 확인되었는데
마찬가지로 정상 종료가 됩니다...ㅠㅠ
실행된 로그를 첨부해드리겠습니다.
시간을 좀 더 늘려보시겠어요?
참고로 저는 H2 데이터베이스를 사용해서 테스트 했습니다.
변경한 전체 코드를 보여드릴게요.
@Test
void JPA_낙관적락_Test() throws InterruptedException {
final int numberOfThreads = 2;
CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads);
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
final String memberId = "memberA";
final int changeMoney = 80;
//스래드 1(트랜잭션1) -> 낙관적 락 조회
executorService.execute(() -> {
try {
log.info("thread1");
memberLockService.findMemberByOPTIMISTIC(memberId);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
});
//스래드 2(트랜잭션2) -> 데이터 수정
executorService.execute(() -> {
// === 추가 ===
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
memberLockService.findMemberAndUpdate(memberId, changeMoney);
countDownLatch.countDown();
});
countDownLatch.await();
}
전체 실행로그
2023-06-08T21:51:31.385+09:00 DEBUG 42009 --- [pool-2-thread-1] o.h.e.t.internal.TransactionImpl : begin
Hibernate:
select
m1_0.member_id,
m1_0.money,
m1_0.version
from
member_lock m1_0
where
m1_0.member_id=?
2023-06-08T21:51:31.489+09:00 DEBUG 42009 --- [pool-2-thread-2] o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
2023-06-08T21:51:31.489+09:00 DEBUG 42009 --- [pool-2-thread-2] o.h.e.t.internal.TransactionImpl : begin
Hibernate:
select
m1_0.member_id,
m1_0.money,
m1_0.version
from
member_lock m1_0
where
m1_0.member_id=?
2023-06-08T21:51:31.491+09:00 DEBUG 42009 --- [pool-2-thread-2] o.h.e.t.internal.TransactionImpl : committing
Hibernate:
update
member_lock
set
money=?,
version=?
where
member_id=?
and version=?
2023-06-08T21:51:36.424+09:00 DEBUG 42009 --- [pool-2-thread-1] o.h.e.t.internal.TransactionImpl : committing
Hibernate:
select
version as version_
from
member_lock
where
member_id=?
2023-06-08T21:51:36.428+09:00 DEBUG 42009 --- [pool-2-thread-1] o.h.e.t.internal.TransactionImpl : rollback() called on an inactive transaction
Exception in thread "pool-2-thread-1" org.springframework.orm.ObjectOptimisticLockingFailureException: Newer version [2] of entity [[hello.jdbc.lock.MemberLock#memberA]] found in database
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:310)
감사합니다.
1
안녕하세요. rnqhstlr2297님
도움을 드리고 싶지만 질문 내용만으로는 답변을 드리기 어렵습니다.
실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.
주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요
추가로 다음 내용도 코멘트 부탁드립니다.
1. 문제 영역을 실행할 수 있는 방법
2. 문제가 어떻게 나타나는지에 대한 상세한 설명
감사합니다.
영한님 항상 강의 잘보고 있습니다~~~ 아래는 구글 드라이브 링크입니다!!
https://drive.google.com/file/d/1edMlUD4V_jdtgESSW4tETlZzDuu-QwPp/view?usp=sharing
test.java.hello.jdbc.lock 경로의 DBLockTest 클래스내에 "JPA_낙관적락_test()메소드" 가 문제의 테스트 영역입니다.
낙관적 락(Optimistic 설정)을 테스트 하려고 합니다. 스레드 1 트랜잭션 커밋 전 스레드 2를 통해 데이터 변경을 진행하였고 트랜잭션 커밋도 진행되었습니다. 그리고 스레드 1의 트랜잭션 커밋전 예외를 기대했지만, 정상 종료가 됩니다!
1
안녕하세요, 인프런 AI 인턴이에요.
테스트 코드를 보면 정상적으로 낙관적 락이 적용되어있고, 스레드 2에서 해당 트랜잭션을 변경하면 스레드 1에서 예외가 발생해야합니다. 하지만 예외가 발생하지 않는 것은 다음과 같은 이유로 인해 발생할 수 있습니다.
① 스프링에서 제공하는 캐쉬 때문으로 인해 캐쉬에서 데이터를 가져왔기 때문에 예외가 발생하지 않았을 가능성이 있습니다.
② 스레드 1과 2가 동시에 트랜잭션을 수행하지 않았을 가능성이 있습니다.
이 부분을 다시 한번 체크해보시고 문제가 있으면 자세한 코드와 로그 내용을 함께 첨부해주시면 더욱 도움이 될 것 같아요.
스프링의 기본 캐쉬를 종속성에 추가하지 않았으면, 기본 캐쉬가 적용되지 않는 것 아닌가요?!
혹시, 스레드 1,2 가 동시에 트랜잭션을 수행하지 않았다는 말씀이 어떤 의미인지 상세히 알 수 있을까요?
스레드 1의 트랜잭션을 시작하고 단순 조회 작업을 실행하고 커밋되기 전, 스레드 2의 트랜잭션이 시작되고 데이터를 변경 후 커밋이 완료되면, 스레드 1의 예외가 발생하는 흐름아닌가요?
트랜잭션이 동시에 수행하지 않는다는 말의 의미를 잘 모르겠습니다.
소스 코드는 위에 코드가 끝이고 로그 내용을 모두 첨부하겠습니다~~
정성스러운 답변 항상 감사드립니다~~
영한님 감사합니다 !!!! ㅎㅎ
제가 제대로 확인하지 않았네요 ㅠㅠㅠ
항상 강의 잘 듣고 있고 새로운 도전 응원합니다~~~~~