OAuth의 JWT를 활용한 로그인 로직을 구현하던 중
UnexpectedRollbackException
을 마주했다.
처음에는 로그의 doFilter
를 보고
OncePerRequestFilter
의 doFilter
에서 액세스 토큰 검증하면서 실패했다고 생각했다.
그래서 곳곳에 print 문도 둬보고,
디버깅 모드로 메서드도 다 뜯어봤지만 도저히 찾을 수 없었다...
그러다 UnexpectedRollbackException
을 키워드로 구글링하던 도중
아래 글을 읽고 진실을 마주하고 말았다...
https://techblog.woowahan.com/2606/
protected AuthToken getToken(UserInfoResponse userinfo) {
long id;
try {
id = userQueryService.getUserBySocialInfo(
userinfo.socialId(),
userinfo.authProvider()
).getId();
} catch (NotFoundException e) {
id = createUser(userinfo.toEntity()).getId();
}
return jwtTokenProvider.issueTokens(id);
}
컨트롤러에서 서비스의 signIn
을 호출하고,
signIn
에서 카카오와 같은 소셜 서비스로부터 사용자 식별자 값을 얻어와 토큰을 발급한다.
이를 위해 signIn
에서는 getToken
을 호출하는데,
타 소셜 서비스를 통해 이미 사용자가 등록되었는지 getUserBySocialInfo
로 확인한다.
이 때 등록되어있지 않은 경우에 NotFoundException
을 던지는데,
이것을 try-catch
문으로 해결하고자 했다.
그런데,,,
위 테코블 글을 읽어보면 알겠지만
신께서 위와 같이 밝히셨다...
위 말씀을 통해 풀어보자면, 언체크 예외인 RuntimeException
이 발생하면
트랜잭션에 rollback이 발생하게 된다는 것이다.
심지어, 나는 서비스 클래스에 @Transactional
을 디폴트 세팅으로 쓰고 있었는데,
@Transacitonal
의 propagation
속성은 디폴트 값이 REQUIRED
이다.
REQUIRED
속성은 트랜잭션 걸린 메서드 안에서 호출하는 메서드들에 대해
동일한 트랜잭션 안에서 수행되도록 한다.
그래서 catch
문에서 createUser
를 호출해
userRepository.save
로 사용자를 등록했던 것이 롤백된다.
(게다가 로그에 insert문까지 나가버림에도 불구하고 롤백되기 때문에
원인을 파악하는 게 한 층 더 힘들었다)
이 문제를 해결하는 방법은 여러 가지고,
맥락에 따라 맞는 것을 선택하면 된다.
(자세한 해결 방법은 하단 링크 참조)
https://keencho.github.io/posts/transaction-rollback/
나의 경우에는 @Transactional
의
noRollbackFor
옵션을 활용했다.
@Transactional(noRollbackFor = NotFoundException.class)
위처럼 롤백을 걸지 않을 예외를 명시하여
RuntimeException
이 발생하더러도 롤백이 발생하지 않고 insert문이 들어간다.