- 회원가입 기능 :
username
,password
를 입력하여 유저 정보 생성 (사용자 id 자동 생성)- 로그인 기능 :
password
를 통해 사용자 인증을 하여 세션을 생성- 필터 기능 : 회원가입과 로그인 기능을 제외한 기능들은 모두 세션이 있어야 정상적으로 작동
- 유저 조회, 삭제 기능
- 일정 생성 : 세션의 사용자
userId
로 일정을 생성- 일정 전체 조회 : 세션의 사용자가 작성한 일정을 전체 조회
- 일정 단건 조회, 수정, 삭제 : 일정의
id
(pk)를 path로 받아 조회, 수정, 삭제
회원가입 기능을 테스트하던 과정에서 두 번째로 생성된 유저였음에도 id
가 3으로 지정된 것을 발견했다.
나는 회원가입을 위해 입력되는 email이 중복되지 않도록 @Column(nullable = false, unique = true)
속성을 추가했는데
이를 확인하기 위해 테스트했던 두 번째 케이스(오류 발생) 때문에 발생하는 문제 같았다.
회원가입 기능의 response에만 오류를 출력하고 이메일이 중복된 채로 id가 2인 유저가 생성이 되었나 확인하기 위해 유저 조회를 돌려보았는데 404가 뜨는 것으로 보아 유저가 생성된 것은 아니었다.
데이터베이스를 들어가서 보자 마찬가지로 id가 2인 유저는 생성되지 않았음을 확인할 수 있었다.
정확한 상황파악을 위해 이메일 중복으로 오류를 여러 차례 내자 유저 생성 오류가 난 만큼 id의 번호가 증가 되어 다음 유저의 id가 생성되는 것을 확인할 수 있었다.
회원 가입(유저 생성)을 하는 코드는 다음과 같았다.
이는 유저와 관련된 기능을 처리하는 service 레이어인데 유저가 db에 저장되는 부분이다.
public SignUpResponseDto signUp(String username, String email, String password) {
User user = new User(username, email, password);
User savedUser = userRepository.save(user);
return new SignUpResponseDto(
savedUser.getId(),
savedUser.getUsername(),
savedUser.getEmail(),
savedUser.getCreatedAt(),
savedUser.getUpdatedAt()
);
}
문제는 아래의 구문에서 JPA Repository를 상속받은 userRepository가 User 엔티티를 저장할 때 생기는 것이었다.
User savedUser = userRepository.save(user);
유저의 id는 @GeneratedValue
로 유저가 db에 저장이 될 때 자동생성이 되도록 설정되어있는데 생성되지 않은 케이스의 id가 생성되는 것으로 보였다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
생성되었으나 존재하지 않는 유저 데이터를 생각해보면 생성되었다가 데이터가 지워졌음을 추론할 수 있다. 즉, 오류가 나서 롤백이 난 것이다.
나는 JPA의 save()
함수에 @transactional
이 포함되어있는지 알아보기로 했다.
결론은 있었다.
즉, save()
과정에서 unique
속성을 가지고 있는 email
필드때문에 이메일을 중복되게 작성하면 오류가 나며 롤백이 일어나지만 그 과정에서 auto_increment
된 id
의 흔적이 남아있었던 것이다.
이를 해결하기 위해 save()
전에 예외를 잡아 처리하였다.
입력한 이메일로 user
를 찾고 이 user
가 존재할 경우 BAD_REQUEST
를 띄웠다.
public SignUpResponseDto signUp(String username, String email, String password) {
Optional<User> findUser = userRepository.findByEmail(email);
if (findUser.isPresent()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "존재하는 이메일입니다.");
}
User user = new User(username, email, password);
User savedUser = userRepository.save(user);
return new SignUpResponseDto(savedUser.getId(), savedUser.getUsername(), savedUser.getEmail(), savedUser.getCreatedAt(), savedUser.getUpdatedAt());
}
이렇게 postman을 통해 유저를 등록할 때에 Id 인덱싱은 해결할 수 있었다.
하지만 의문이 생겼다.
만약 정말 save()
의 문제였다면 sql 콘솔창으로 직접 데이터를 insert
했을 때 정상적으로 인덱스가 올라갔을 것이다. 그러나 직접 입력했을 때도 오류케이스의 인덱스를 건너뛰고 올라가는 것을 알게 되었다.
즉, save()
만의 문제가 아니었던 것이다.
결론적으로, 이 문제의 원인은 auto_increment
에 있었다.
원래 sql에서 insert
를 하기 전에 auto_increment
가 먼저 되게끔 설정이 되어있기 때문에 오류가 나도 그 번호의 흔적이 남으며 롤백이 되게 된다.
사실 실무에서 id가 오류때문에 몇번을 건너뛰고 올라가든 그렇게 중요할 것 같지는 않다는 게 튜터님의 의견이었다. id
가 결국 등록되기만 하면 되고 이 고유 pk를 통해 다른 기능들을 사용하는 것이 더 중요하기 때문일 것이다.
하지만 이런 트러블슈팅 과정을 통해 save()
에 @transactional
속성이 있다는 것도 알게 되었고 sql에 auto_increment
가 insert
보다 선행된다는 것도 알게 되어 유익했다.