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