본문 바로가기
프로젝트 회고

JPA Schedule 프로젝트: 구현 중 발생한 트러블슈팅

by taeung515 2025. 5. 23.

AUTO_INCREMENT와 트랜잭션 롤백 시 ID 증가 문제

이번 프로젝트에서 User id는 AUTO_INCREMENT 방식으로 DB가 관리하도록 했습니다. 회원가입 시 email을 중복으로 입력해서 실패하면 에러가 납니다. 여기까지는 당연한 동작입니다.
문제는 그 다음입니다. 중복 이메일을 수정하고 다시 회원가입을 시도하면 id 값이 하나 건너뛰어 증가합니다. 예를 들어, 마지막 id가 1이었으면 실패 후 다음 성공 시 id가 3이 되는 식입니다.

이해를 돕기 위한 사진

더보기
정상요청
이메일 중복으로 생성 실패
id :2를 건너뛰고 3으로 생성된 상황



@Transactional을 적용하면 롤백될 때 모든 것이 되돌려질 거라 생각했는데 id가 왜 증가했을까?

이 현상은 데이터베이스의 AUTO_INCREMENT 시퀀스가 트랜잭션과 별개로 동작하기 때문입니다.

시퀀스 값 미리 할당: INSERT를 시도할 때마다 DB는 AUTO_INCREMENT 시퀀스에서 다음 ID 값을 미리 가져와서 할당합니다.

트랜잭션 롤백과 시퀀스: INSERT가 실패(유니크 제약 조건 위반)하여 트랜잭션이 롤백되면 실제 데이터는 삽입되지 않습니다. 하지만 이미 할당된 AUTO_INCREMENT 시퀀스 값은 되돌려지지 않고 소비된 상태로 남습니다. AUTO_INCREMENT 시퀀스 값의 증가는 트랜잭션의 일부가 아닌, 데이터베이스 자체의 내부 메커니즘입니다.

대부분의 RDBMS는 시퀀스 값의 롤백을 지원하지 않습니다. 이는 시퀀스 관리의 성능 저하를 막고, 동시성 및 분산 환경에서 시퀀스 값의 일관성을 유지하기 위한 의도된 설계입니다. 따라서 애플리케이션 레벨에서 이 AUTO_INCREMENT 값의 건너뛰기를 직접적으로 막을 수는 없었습니다.


DTO 분리 ?? Groups 사용??

프로젝트 초반에는 @Valid 기반 유효성 검증을 도입하기 전이라 회원가입과 회원정보 수정을 동일한 RequestDto 하나로 처리했습니다. 회원가입 시에는 username, email, password가 모두 필수였지만 회원정보 수정 시에는 이들 항목이 선택적으로 들어올 수 있었습니다. 서비스 계층에서는 update(username, email, password) 형태로 파라미터를 받아 String.isBlank()로 변경할 값만 적용했지만 @Valid를 적용한 뒤에는 같은 DTO에 서로 다른 유효성 규칙을 섞어 쓰기 어려웠습니다.

이 문제를 해결하기 위해 CreateUserRequestDtoUpdateUserRequestDto처럼 요청 목적에 따라 DTO를 분리하거나 Validation Groups를 활용하는 두 가지 방법을 검토했고, DTO를 분리하는 방법을 선택했습니다. DTO 이름만 봐도 역할과 검증 규칙이 명확해져 유지보수성과 가독성이 좋아지기 때문입니다. 앞으로는 기능별로 적절한 DTO를 사용해 각 요청에 맞는 유효성 검증을 적용할 계획입니다.


orphanRemoval사용?? Service계층에서의 로직 구현???

User와 Schedule 엔티티 연관관계를 설정한 후 User 삭제 시 Schedule도 함께 삭제되도록 구현해야 했습니다. orphanRemoval 를 사용할지 아니면 Service 계층에서 ScheduleRepository를 직접 호출해 명시적으로 삭제 흐름을 드러낼지 고민했습니다. 비즈니스 로직을 한곳에서 확인할 수 있도록 후자를 선택하여, UserService에 ScheduleRepository를 주입하고 delete(Long userId) 메서드에서 먼저 scheduleRepository.deleteAllByUserId(userId)를 호출한 뒤 userRepository.delete(user)를 수행하도록 코딩했습니다.

이 메서드는 @Transactional로 묶여 있어 일정 삭제와 사용자 삭제가 하나의 트랜잭션 안에서 안전하게 처리됩니다. 테스트 시에도 삭제 순서를 제어하며 검증하기 쉽고, 코드만 봐도 유저 삭제 시 일정도 함께 삭제된다는 흐름이 바로 이해되기 때문에 유지보수성과 가독성이 모두 향상되었습니다.


JWT와 Session

이번 프로젝트에서는 세션 기반 인증 방식을 직접 구현해 보고, 그 작동 원리를 체득하기 위해 다음과 같은 흐름을 따라 작업했습니다.

1. 세션 활용 학습 목적

  1. Spring MVC 컨트롤러에서 HttpSession을 파라미터로 받아
  2. 로그인 시 session.setAttribute()로 사용자 식별자를 저장하고,
  3. 로그아웃, 회원 탈퇴 시 session.invalidate()로 무효화해 보는 과정을 통해
  4. 서버가 클라이언트 상태를 어떻게 관리하는지를 단계별로 익혔습니다.

2. 세션 기반 인증

  1. 저장 위치: 서버 메모리(Session Storage)에 모든 사용자 정보를 보관
  2. 클라이언트 쿠키: 오직 세션 ID(JSESSIONID)만 전달
  3. Stateful: 요청마다 서버에 저장된 세션을 조회 -> 인증,인가 결정
장점 단점
세션 무효화(로그아웃, 강제 만료)가 쉽고 즉시 반영 서버 메모리에 사용자 수만큼 리소스 소모
서버에서 사용자 상태를 일원 관리하므로 보안 사고 시 대응 용이 여러 대의 서버를 운영할 때 세션 동기화·로드밸런싱 추가 작업 필요

3. JWT(Json Web Token) 기반 인증

  1. 저장 위치: 토큰 자체를 클라이언트(로컬스토리지 또는 쿠키)에 보관
  2. Stateless: 서버에는 토큰 검증을 위한 비밀 키만 있고, 별도 상태 저장소가 없음
장점 단점
서버 확장성(분산 시스템) 극대화: 모든 요청이 자체 토큰으로 인증 가능 토큰 탈취 시 만료 전까지 무단 사용 위험
서버 메모리 부하 거의 없음 토큰 무효화(로그아웃)는 별도의 블랙리스트 관리 필요
토큰 크기(헤더+페이로드+서명)가 커서 네트워크 부담 증가

다음 단계로는 JWT 기반 인증을 직접 구현해 보며 “Stateless 아키텍처” 의 이점을 체험할 예정입니다.

이 과정을 통해 두 방식의 장단점을 명확히 비교하고, 실제 서비스 요구에 따라 적재적소에 인증 방식을 선택할 수 있는 실력을 기르는 것이 목표입니다!