해결된 질문
작성
·
151
1
안녕하세요 강사님, 좋은 강의 잘 듣고 있습니다.
바쁘실텐데도 불구하고 답변해주셔서 감사합니다.
질문은 크게 2가지 입니다.
유니크 제약 조건 위반시 예외처리방법
like() 로직에선 article_id와 user_id로 유니크 제약 조건을 걸어서 한 명의 사용자가 중복 생성하는 것을 막고 있습니다. 이 유니크 제약 조건을 위반하여 발생하는 에러는 @ControllerAdvice에서 @ExceptionHandler(DataIntegrityViolationException.class) 로 시작하는 핸들러를 통해서 처리하는 것이 일반적인가요?
2.unlike()에서 한 명의 사용자가 삭제 중복요청시 발생하는 동시성 문제
@Transactional
public void unlikePessimisticLock2(Long articleId, Long userId) {
articleLikeRepository.findByArticleIdAndUserId(articleId, userId)
.ifPresent(articleLike -> {
articleLikeRepository.delete(articleLike);
ArticleLikeCount articleLikeCount = articleLikeCountRepository.findLockedByArticleId(articleId).orElseThrow();
articleLikeCount.decrease();
});
}
한 명의 사용자가 "좋아요" 중복 생성 요청하는 것을 방어하는 로직이 있으니 "좋아요 해제" 중복 요청에 대한 고려에 대해서 생각해봤습니다. 이 로직에선 findLockedByArticleId 에서 베타락을 걸게 되어서 하나의 article에 대한 여러 사용자의 "좋아요 해제" 요청시의 동시성 문제를 방어하고 있습니다. 그런데 한 명의 사용자가 "좋아요 해제" 중복 요청 시에 ifPresent 구문을 통과하게 되어 articleLikeRepository.delete(articleLike);를 여러번 수행하게 됩니다. 여기서 delete는 멱등성이 보장되니 큰 문제 없이 넘어가겠지만 그 아래 count감소 로직은 해당 트랜잭션들이 베타락을 획득할 때마다 계속 수행되는 것이 아닌지 궁금합니다. 일단 ifPresent 구문 안으로만 들어오면 count감소는 무조건 일어나게 되니 문제가 생기는게 아닐까요? 그래서 ifPresent구문 전에 락을 걸기 위해 findByArticleIdAndUserId() 여기서 락획득이 이루어져야 하는건 아닌지 궁금합니다.
작성하다 보니 질문이 길어져서 죄송합니다.
답변 2
1
성호님, 안녕하세요!
귀중한 시간 내서 열심히 들어주시니 감사하네요~~!
1.
강의에서는 별다른 예외 처리 로직을 넣진 않았지만,
실무에서는 말씀하신대로 예외 처리는 Advice와 ExceptionHandler 정의하여 단일 지점에서 관리하는게 일반적입니다!
DataIntegrityViolationException.class 를 ExceptionHandler에서 받아줄 수도 있고,
save 호출하는 메소드에서 즉시 try~catch로 DataIntegrityViolationException.class 예외 캐치하여, 직접 정의한 커스텀 예외 클래스로 한번 래핑해서 던질 수도 있습니다. (예외 클래스를 단일하게 관리하고 싶을 때 이렇게 처리하기도 합니다!)
이 부분은 자유롭게 구현하시면 될 것 같네요!
2.
아이고, 이 부분은 말씀하신게 맞습니다..!
강의에 오류가 있었네요.. ㅠㅠ
말씀하신대로 지금 구조에서는 count가 여러번 수행될 수 있겠네요.
해결 방법으로는, 의견 주신 ArticleLike 조회 시점부터 배타락을 거는 방법도 있겠고, delete 쿼리 수행 결과가 반환되도록 쿼리 메소드를 새롭게 정의하여 실제 삭제가 수행되지 않았으면 예외를 던지는 등 다양한 방법이 있을 것 같습니다..!
성능적으로는 락을 걸지 않는 후자가 유리할 것 같네요.
생성 부분에만 집중하다보니, 후에 카운트 로직 추가할 때에 삭제에 대해서 제대로 검수하지 못했습니다..
이 부분은 따로 수정해두도록 하겠습니다..!
꼼꼼하게 잘 살피면서 제보해주셔서 감사하고, 강의 수강에 불편을 드려서 죄송합니다..!
0
안녕하세요, 인프런 AI 인턴입니다. 차성호님.
질문해주신 내용은 데이터베이스에서의 동시성 문제와 예외 처리에 관련된 사항입니다. 특히, “좋아요” 기능에서의 유니크 제약과 동시성 문제에 대한 해결 방법에 대해 질문 주셨네요. 이를 간단히 정리해서 답변 드리겠습니다.
DataIntegrityViolationException
은 주로 데이터베이스의 유니크 제약 조건 위반 시 발생하는 예외입니다. @ControllerAdvice
와 @ExceptionHandler
를 통해 해당 예외를 처리하는 것은 일반적인 패턴입니다. 이를 통해 특정한 에러 메시지를 사용자에게 전달하거나, 로깅 등의 추가적인 처리를 할 수 있습니다.unlikePessimisticLock2
메서드에서는 findLockedByArticleId
을 통해 비관적 락을 사용하여 동시성 문제를 방어하고 있습니다.
문제 포인트: 말씀하신 것처럼, 동일한 사용자에 의해 중복 “좋아요 해제” 요청이 들어올 경우, ifPresent
구문을 통과하면 articleLikeRepository.delete(articleLike)
는 문제가 없으나, articleLikeCount.decrease()
로직에서 비관적 락이 걸리기 때문에 decrease
호출이 중복 수행될 위험이 있습니다.
해결 방안 제시:
findByArticleIdAndUserId
메서드에서 비관적 락을 사용하여, 엔티티를 획득할 때 이미 해당 엔티티가 삭제되지 않았음을 보장하도록 구현할 수 있습니다. 이를 통해 해당 엔티티가 이미 삭제된 상태에서는 decrease
로직이 수행되지 않도록 할 수 있습니다.decrease
메서드에서 이미 처리되지 않은 경우에 대비한 체크 로직을 추가하여 중복 처리를 회피할 수 있습니다.위 문제를 해결하기 위한 더 많은 사례와 정보를 아래에서 찾아볼 수 있습니다:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.