문제

Duplicate entry '325' for key 'token.UK_c56184j4djjqx16jwprg167qp'

이전에 개발하던 개인 프로젝트에서 @Transactional에 대한 이해가 부족하여 발생한 문제이다.

아래는 문제가 발생한 코드의 일부이다.

UserService

@Transactional
public Long userLogin(UserForm userForm) {
    User user = userRepository.findByEmail(userForm.getEmail())
            .orElseThrow(InvalidSigninInformation::new);

    if(!scryptPasswordEncoder.matches(userForm.getPassword(), user.getPassword())) {
        throw new InvalidSigninInformation();
    }

    if(!user.getTokens().isEmpty()) {
        tokenRefresher.removeRefreshToken(user);
    }

    tokenRefresher.addRefreshToken(user);

    return user.getId();
}

TokenRefresher

@Transactional
public void removeRefreshToken(User user) {
    tokenRepository.deleteByUserId(user.getId());
}

@Transactional
public void addRefreshToken(User user) {
    user.addToken(jwtTokenProvider.createRefreshToken(user.getId()));
}

서로 다른 클래스에서 각 메서드에 @Transactional을 사용하였고, removeRefreshToken 동작과 addRefreshToken가 같은 트랜잭션 내에서 동작하여 발생한 문제이다.

@Transactional을 기본적으로 Required 전파레벨을 사용하는데, 이는 기존 사용하던 트랜잭션이 있다면, 그 트랜잭션에 합류하여 작업을 같이 처리한다.

위 코드에서 볼 수 있다시피 기본 전파레벨을 선택했고 이로인해 removeRefreshToken 이 적용되지 않은 채로 addRefreshToken 을 통해 새로운 Token을 추가하려 했기 때문에 문제가 발생한 것이다.

해결

전파레벨 설정

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void removeRefreshToken(User user) {
    tokenRepository.deleteByUserId(user.getId());
}

@Transactional
public void addRefreshToken(User user) {
    user.addToken(jwtTokenProvider.createRefreshToken(user.getId()));
}

REQUIRES_NEW 옵션은 기존 트랜잭션이 존재하더라도 새로운 트랜잭션을 만들어 처리하기 때문에, 삭제 작업이 있을 경우 처리하여 적용되도록 할 수 있다.

정리