[spring-advanced] Lv5. 문제 정의와 해결 과정

김유란·2025년 2월 26일

💡 Spring 심화 주차 개인 과제 Lv5 해결 과정 기록

1. [문제 인식 및 정의]

2. [해결 방안]
	2-1. [의사결정 과정]
	2-2. [해결 과정]
	
3. [해결 완료]
	3-1. [회고]
	3-2. [전후 데이터 비교]

1️⃣ 문제 인식 및 정의

signup() 메서드의 기존 코드에서는 userRepository.existsByEmail() 호출 시 SELECT 쿼리가 실행되고 save() 호출 시 INSERT 쿼리가 실행되어 2개의 쿼리가 발생하고 있습니다.
User Entity의 email 속성이 @Column(unique = true)private String email;로 Unique 제약 조건이 적용되어 있으므로 SELECT를 생략하고 save()에서 예외 처리를 하는 것이 성능적으로 더 좋을 것 같다고 생각하였습니다.

@Transactional
public SignupResponse signup(SignupRequest signupRequest) {

    if (userRepository.existsByEmail(signupRequest.getEmail())) {
        throw new InvalidRequestException("이미 존재하는 이메일입니다.");
    }

    String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());

    UserRole userRole = UserRole.of(signupRequest.getUserRole());

    User newUser = new User(
            signupRequest.getEmail(),
            encodedPassword,
            userRole
    );
    User savedUser = userRepository.save(newUser);

    String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);

    return new SignupResponse(bearerToken);
}

2️⃣ 해결 방안

2-1. 의사결정 과정

 1. 기존 코드 유지

    장점:
        - 코드가 직관적
		- 예외 발생 시 아래 코드 생략 가능
    
    단점:
        - SELECT 쿼리 추가 발생

 2. save() 에서 UNIQUE 제약 조건 위반 시 예외 처리

    장점:
        - 불필요한 SELECT 쿼리 제거 가능
        - 동시 요청이 많을 때 SELECT 생략하면 DB 부하 감소
    단점:
        - save()가 예외를 던지기 전까지  인코딩과 객체 생성 코드 실행됨

🔹2번째 방법을 사용하면 매번 비밀번호 인코딩과 객체 생성이 발생합니다. 그러나 데이터베이스 I/O 비용이 CPU 연산보다 크며 동시 요청이 많을수록 중복 검사를 위한 SELECT 쿼리가 병목될 가능성이 있으므로 성능 최적화를 위해 save() 에서 중복 검사를 수행하는 방법을 선택하였습니다.

2-2. 해결 과정

@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
    String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());

    UserRole userRole = UserRole.of(signupRequest.getUserRole());

    User newUser = new User(
            signupRequest.getEmail(),
            encodedPassword,
            userRole
    );
        
    User savedUser;
    try {
        savedUser = userRepository.save(newUser);
    } catch (DataIntegrityViolationException e) {
        throw new InvalidRequestException("이미 존재하는 이메일입니다.");
    }
        
    String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);

    return new SignupResponse(bearerToken);
}
  • save() 메서드를 호출하여 바로 INSERT를 시도하고 DB의 UNIQUE 제약 조건을 활용하여 중복을 검사하도록 수정하였습니다.
  • 제약 조건을 위반하면 RuntimeException이 발생하고 트랜잭션이 롤백되어 DB 상태가 유지됩니다.

3️⃣ 해결 완료

3-1. 회고

SELECT 후 INSERT 하는 경우 완벽한 동시성 제어가 불가능하다는 점과 데이터베이스 I/O(입출력) 연산은 비용이 크기 때문에 이를 줄이는 것이 성능 최적화의 핵심이라는 것을 알게되었습니다.

3-2. 전후 데이터 비교

  • 수정 전

    • SELECT + INSERT ⇒ 쿼리 2개 실행

  • 수정 후

    • INSERT 쿼리 1개 실행

0개의 댓글