유저 RefreshTokenRepository에서 accessToken과 refreshToken 을 이용해서 데이터를 삭제해주는 로직을 작성하였습니다.
// 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);
}
그런데 실제 서비스에서 로그아웃시, 다음과 같은 에러를 발생하였습니다.
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 어노테이션(비관적 락)을 추가해주었습니다.
// 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 |