유저 RefreshTokenRepository에서 accessToken과 refreshToken 을 이용해서 데이터를 삭제해주는 로직을 작성하였습니다.
<shell />// Service @Transactional public void tokenRemove(HttpServletResponse response, String accessToken, String refreshToken) { // 쿠키에서 삭제 -> 클라이언트에게 감 log.info("토큰 삭제"); tokenCookieRemove(response, TYPE_ACCESS); tokenCookieRemove(response, TYPE_REFRESH); refreshTokenRepository.deleteByAccessTokenAndRefreshToken(accessToken, refreshToken); // 서버에서 삭제 -> 비교군 } // Repository public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { void deleteByAccessTokenAndRefreshToken(String accessToken, String refreshToken); }
그런데 실제 서비스에서 로그아웃시, 다음과 같은 에러를 발생하였습니다.
<shell />2024-06-24T09:58:14.123Z ERROR 1 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.restgram.domain.user.entity.RefreshToken#9]] with root cause org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.restgram.domain.user.entity.RefreshToken#9]
이러한 StaleObjectStateException은 이미 삭제한 데이터를 다시 삭제하려고 할 경우 발생하는 에러입니다.
따라서 저는 객체를 가져온 후 삭제하는 방식으로 수정한 후, Pessimistic_write 어노테이션(비관적 락)을 추가해주었습니다.
<shell />// service @Transactional public void tokenRemove(HttpServletResponse response, String accessToken, String refreshToken) { // 쿠키에서 삭제 -> 클라이언트에게 감 log.info("토큰 삭제"); tokenCookieRemove(response, TYPE_ACCESS); tokenCookieRemove(response, TYPE_REFRESH); Optional<RefreshToken> optionalRefreshToken = refreshTokenRepository.findByAccessTokenAndRefreshToken(accessToken, refreshToken); if (optionalRefreshToken.isPresent()) refreshTokenRepository.delete(optionalRefreshToken.get()); } // repository public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { @Lock(value = LockModeType.PESSIMISTIC_WRITE) Optional<RefreshToken> findByAccessTokenAndRefreshToken(String accessToken, String refreshToken); boolean existsByAccessTokenAndRefreshToken(String accessToken, String refreshToken); }
'프로젝트' 카테고리의 다른 글
[Redis] Redis에 RefreshToken 저장하기 - 설치, Spring boot 적용 (1) | 2024.09.22 |
---|---|
[Spring/MongoDB] 스프링 프로젝트에 MongoDB 연결하기 (설치 및 연결) (0) | 2024.07.12 |
[QueryDSL] 피드의 각 첫번째 이미지 가져오는 쿼리 개선하기 (0) | 2024.06.13 |
[Spring/QueryDSL] Fetch Join이랑 limit은 함께 못쓴다고요 ? (1) | 2024.06.13 |
[Java] Record란? & 프로젝트에 Record 적용해보기 (0) | 2024.06.12 |