Project

[Project] @Transactional + CustomException을 Controll하자

woo0doo 2023. 8. 8. 11:27

Project 하던 중, 정상적인 값이 조회되는데 DB값은 안바뀌는 상황이 있었다. 그에 대해 이야기 해보려고 한다.

 

전제

 안드로이드 측에서 kakao accessToken을 주면

  • DB에 kakaoUser가 이미 존재 -> 바로 서버 accessToken 및 refreshToken 발급 (httpstatus 200 내려줌)
  • DB에 kakaoUser가 존재 x 즉 첫 방문 User -> signToken을 발급해서 닉네임, 이메일 등 부가 정보를 입력하는 화면으로 이동 후, 앞서 발급한 signToken + 부가 정보를 받아서 회원 가입 (httpstatus 404 내려줌 -> 즉 exception 터트림)

+ 추가적으로 만약 회원 탈퇴를 할 시, 중복 가입 방지를 위해 탈퇴한 회원은  한달 동안 가입을 못하게 방지해놓았음.


    @Transactional
    public AuthMessage loginAccess(SocialLoginRequest socialLoginRequest) {
        OAuthSocialEmailResponse response = fetchSocialEmail(socialLoginRequest);	//kakaoId가져오기
        String socialEmail = response.socialEmail();

        checkIsSleepingUser(socialEmail); //휴면 유저인지 확인
        Optional<User> findUser = userRepository.findBySocialEmail(socialEmail);
        checkIsRevokeUser(socialEmail);	//탈퇴 유저인지 확인 **
        AuthMessage loginMessage;
        if(findUser.isPresent()) {
            User user = findUser.get();
            checkUserStatus(user);
            user.updateLastLoginDate();
            user.updateFcmToken(socialLoginRequest.fcmToken());

            JwtDto jwtDto = login(LoginRequest.toLoginRequest(user));
            loginMessage = new AuthMessage(
                    jwtDto,
                    StatusCode.LOGIN.getMessage()
            );
        } else {
            String signToken = jwtProvider.createSignToken(socialEmail);
            SignToken signTokenResponse = new SignToken(signToken);
            log.info("signTokenResponse={}", signTokenResponse.signToken());
            throw new AuthException(StatusCode.NEED_TO_SIGNUP, signTokenResponse); // SignToken 반환
        }
        return loginMessage;
    }

다른 코드들은 상관이 없고, checkIsRevokeUser 쪽 코드를 보면 

@Transactional
    public void checkIsRevokeUser(String socialEmail) {
        Optional<RevokeUser> checkUser = revokeUserRepository.findBySocialEmail(customEncryptUtil.hash(socialEmail));
        if (checkUser.isEmpty()) return;
        RevokeUser revokeUser = checkUser.get();
        boolean isRevokedExpired = LocalDateTime.now().isAfter(revokeUser.getRevokedAt().plusMonths(1));
        if (isRevokedExpired) {
            revokeUser.deleteRevokeUser();
        }
        else throw new UserException(StatusCode.REVOKE_USER);
    }

 

@Entity
@AllArgsConstructor
@Builder
@NoArgsConstructor
@Getter
public class RevokeUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "revoke_user_id")
    private Long id;

    private String socialEmail;
    private LocalDateTime revokedAt;

    public void deleteRevokeUser() {
        this.socialEmail=null;
        this.revokedAt=null;
    }
}

따라서 이와같이 revoke유저가 재가입을 요청할 때, 한달이 넘었는지 확인하고 revokeUser에 있는 user정보를 삭제하도록 함. 하지만 이때 revokeUser의 값이 null로 안바뀌는 문제가 발생함.

 

왜 ?? 

 

필자는 위의 사진과 같이 정상적인 값이 잘 나오는 것을 인지하고, @Transaction의 문제인지 생각도 하지 못했다....

한 2일정도 앓다가 유레카를 외쳤었다..! ㅋㅋㅋㅋㅋㅋㅋ

순서를 보자면

 

revokeUser는 탈퇴한 유저이기 때문에 User Table의 값이 없다. 따라서 signToken을 발급해준다. 이것은 앞서 말했듯이 404라는 exception을 터트림

 

transaction은 무엇인가? -> 로직을 수행할 때, 예외를 만나면 rollback을 함으로써 db의 값을 보존시켜줌

내가 커스텀한 exception이어도 내가 예상한 값이 반환이 되어도, exception은 exception인 것이다. 따라서 rollback이 되고있던 것이였다...!!!

 

따라서 내가 한 조치는

 

@Transactional(noRollbackFor = {AuthException.class})

회원가입 기능을 하는 매서드에 noRollbackFor을 추가했다 ! 

저기능은 내가 커스텀한 Exception(AuthException)만을 제외하고 문제가 터질시 rollback시켜주는 기능이였다.

저 한줄을 추가하니 null로 안바뀌던 DB가 null로 바뀌었다.

 


깨달은점 :

@Transaction의 남용 금지. 내 Exception도 컨트롤 하면서 사용하자..

내가 예상한 정상적인 값이여도 내가 터트린 Exception인지 의심하자..

유레카는 엄청난 희열이구나..