241115 개인과제 4 트러블슈팅-1

물고기가자라면어그로·2024년 11월 15일
0

네 번째 과제는 JPA를 사용하여 일정 관리 앱의 유저와 일정에 대한 CRUD를 구현하고 쿠키와 세션을 사용하여 로그인 기능(인증)을 구현하는 것이었다.

구현기능 (배경)

  1. 회원가입 기능 : username, email, password를 입력하여 유저 정보 생성 (사용자 id 자동 생성)
  2. 로그인 기능 : emailpassword를 통해 사용자 인증을 하여 세션을 생성
  3. 필터 기능 : 회원가입과 로그인 기능을 제외한 기능들은 모두 세션이 있어야 정상적으로 작동
  4. 유저 조회, 삭제 기능
  5. 일정 생성 : 세션의 사용자 userId로 일정을 생성
  6. 일정 전체 조회 : 세션의 사용자가 작성한 일정을 전체 조회
  7. 일정 단건 조회, 수정, 삭제 : 일정의 id(pk)를 path로 받아 조회, 수정, 삭제

트러블슈팅

1. 발단

회원가입 기능을 테스트하던 과정에서 두 번째로 생성된 유저였음에도 id가 3으로 지정된 것을 발견했다.

2. 전개

나는 회원가입을 위해 입력되는 email이 중복되지 않도록 @Column(nullable = false, unique = true)속성을 추가했는데
이를 확인하기 위해 테스트했던 두 번째 케이스(오류 발생) 때문에 발생하는 문제 같았다.

회원가입 기능의 response에만 오류를 출력하고 이메일이 중복된 채로 id가 2인 유저가 생성이 되었나 확인하기 위해 유저 조회를 돌려보았는데 404가 뜨는 것으로 보아 유저가 생성된 것은 아니었다.

데이터베이스를 들어가서 보자 마찬가지로 id가 2인 유저는 생성되지 않았음을 확인할 수 있었다.

정확한 상황파악을 위해 이메일 중복으로 오류를 여러 차례 내자 유저 생성 오류가 난 만큼 id의 번호가 증가 되어 다음 유저의 id가 생성되는 것을 확인할 수 있었다.

3. 위기

회원 가입(유저 생성)을 하는 코드는 다음과 같았다.
이는 유저와 관련된 기능을 처리하는 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_incrementid의 흔적이 남아있었던 것이다.

4. 절정

이를 해결하기 위해 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()만의 문제가 아니었던 것이다.

5. 결말

결론적으로, 이 문제의 원인은 auto_increment에 있었다.
원래 sql에서 insert를 하기 전에 auto_increment가 먼저 되게끔 설정이 되어있기 때문에 오류가 나도 그 번호의 흔적이 남으며 롤백이 되게 된다.

마무리

사실 실무에서 id가 오류때문에 몇번을 건너뛰고 올라가든 그렇게 중요할 것 같지는 않다는 게 튜터님의 의견이었다. id가 결국 등록되기만 하면 되고 이 고유 pk를 통해 다른 기능들을 사용하는 것이 더 중요하기 때문일 것이다.
하지만 이런 트러블슈팅 과정을 통해 save()@transactional 속성이 있다는 것도 알게 되었고 sql에 auto_incrementinsert보다 선행된다는 것도 알게 되어 유익했다.

0개의 댓글