@Test
@DisplayName("낙관적 락 테스트")
public void optimistic_test()throws InterruptedException{
//given
int threadCount = 20;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
System.out.println("초기 좋아요 수:"+playlistArticleRepository.findById(articleId).orElseThrow().getLikeCnt());
for(int i=0;i<threadCount;i++){
final Member member = members.get(i);
executorService.submit(()->{
try{
playListArticleService.decreaseLike(articleId,member.getEmail());
System.out.println("좋아요 취소: "+member.getEmail());
} catch(Exception e){
System.out.println("감소 실패 에러: "+e.getMessage());
}finally {
latch.countDown();
}
});
}
latch.await();
//then
PlayListArticle playListArticle = playlistArticleRepository.findById(articleId).orElseThrow();
assertThat(playListArticle.getLikeCnt()).isEqualTo(0);
assertThat(playListArticle.getLikeList()).isNull();
}
초기 좋아요 수:20
좋아요 취소: user11@gmail.com
좋아요 취소: user12@gmail.com
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
....
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
감소 실패 에러: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.FifthSpring.model.PlayListArticle#13]
org.opentest4j.AssertionFailedError:
expected: 0
but was: 18
Expected :0
Actual :18
좋아요 취소에 성공한 쿼리부터 살펴보자.
update
play_list_article
set
address=?,
created=?,
latitude=?,
like_cnt=?,
longitude=?,
member_id=?,
updated=?,
version=?,
view_cnt=?
where
id=?
and version=?
좋아요 취소에 실패했을 때의 예외메시지를 다시 살펴보자면
Row was updated or deleted by another transaction 즉 또 다른 transaction의 변경 및 삭제로 인해 18개의 요청이 유실되었음을 알 수 있다.
낙관적 락은 이렇듯 예외를 catch해서 다시 retry를 할 수 있다.
@EnableRetry 적용@Retryable 어노테이션으로 자동으로 재시도하는 로직을 구현 @Transactional(propagation = Propagation.REQUIRES_NEW)
@Retryable(value={ObjectOptimisticLockingFailureException.class}
, maxAttempts = 100,backoff = @Backoff(delay=50))
public PlayListDto decreaseLike(Long id,String email) {
/* 낙관적 락 */
PlayListArticle targetPost = playlistArticleRepository.findByIdWithOptimisticLock(id).orElseThrow();
if (targetPost.getLikeCnt() <=0 )
{
throw new IllegalArgumentException("likeCnt는 0 이하가 될 수 없습니다");
}
targetPost.decreaseLike(); //엔티티 내부에서 likeCnt 처리
Member member = memberRepository.findByEmail(email).orElseThrow();
likeRepository.deleteByPlayListArticleIdAndMember(id,member.getId()); // member엔티티 변경
return mapToPlayListDto(targetPost);
}
