트랜잭션 분석을 통한 UnexpectedRollbackException 해결

konu·2024년 5월 30일
0

스프링

목록 보기
7/8

 

 

 

0. 배경

OAuth의 JWT를 활용한 로그인 로직을 구현하던 중
UnexpectedRollbackException을 마주했다.

처음에는 로그의 doFilter를 보고
OncePerRequestFilterdoFilter에서 액세스 토큰 검증하면서 실패했다고 생각했다.

그래서 곳곳에 print 문도 둬보고,
디버깅 모드로 메서드도 다 뜯어봤지만 도저히 찾을 수 없었다...

그러다 UnexpectedRollbackException을 키워드로 구글링하던 도중
아래 글을 읽고 진실을 마주하고 말았다...

https://techblog.woowahan.com/2606/

 

 

 

1. 원인

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을 디폴트 세팅으로 쓰고 있었는데,
@Transacitonalpropagation 속성은 디폴트 값이 REQUIRED이다.

REQUIRED 속성은 트랜잭션 걸린 메서드 안에서 호출하는 메서드들에 대해
동일한 트랜잭션 안에서 수행되도록 한다.

그래서 catch 문에서 createUser를 호출해
userRepository.save로 사용자를 등록했던 것이 롤백된다.

(게다가 로그에 insert문까지 나가버림에도 불구하고 롤백되기 때문에
원인을 파악하는 게 한 층 더 힘들었다)

 

 

 

2. 해결

이 문제를 해결하는 방법은 여러 가지고,
맥락에 따라 맞는 것을 선택하면 된다.

(자세한 해결 방법은 하단 링크 참조)
https://keencho.github.io/posts/transaction-rollback/

나의 경우에는 @Transactional
noRollbackFor 옵션을 활용했다.

@Transactional(noRollbackFor = NotFoundException.class)

위처럼 롤백을 걸지 않을 예외를 명시하여
RuntimeException이 발생하더러도 롤백이 발생하지 않고 insert문이 들어간다.

profile
日日是好日

0개의 댓글