목차
AUTO_INCREMENT와 트랜잭션 롤백 시 ID 증가 문제
이번 프로젝트에서 User id는 AUTO_INCREMENT 방식으로 DB가 관리하도록 했습니다. 회원가입 시 email을 중복으로 입력해서 실패하면 에러가 납니다. 여기까지는 당연한 동작입니다.
문제는 그 다음입니다. 중복 이메일을 수정하고 다시 회원가입을 시도하면 id 값이 하나 건너뛰어 증가합니다. 예를 들어, 마지막 id가 1이었으면 실패 후 다음 성공 시 id가 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에 서로 다른 유효성 규칙을 섞어 쓰기 어려웠습니다.
이 문제를 해결하기 위해 CreateUserRequestDto와 UpdateUserRequestDto처럼 요청 목적에 따라 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. 세션 활용 학습 목적
- Spring MVC 컨트롤러에서 HttpSession을 파라미터로 받아
- 로그인 시 session.setAttribute()로 사용자 식별자를 저장하고,
- 로그아웃, 회원 탈퇴 시 session.invalidate()로 무효화해 보는 과정을 통해
- 서버가 클라이언트 상태를 어떻게 관리하는지를 단계별로 익혔습니다.
2. 세션 기반 인증
- 저장 위치: 서버 메모리(Session Storage)에 모든 사용자 정보를 보관
- 클라이언트 쿠키: 오직 세션 ID(JSESSIONID)만 전달
- Stateful: 요청마다 서버에 저장된 세션을 조회 -> 인증,인가 결정
| 장점 | 단점 |
| 세션 무효화(로그아웃, 강제 만료)가 쉽고 즉시 반영 | 서버 메모리에 사용자 수만큼 리소스 소모 |
| 서버에서 사용자 상태를 일원 관리하므로 보안 사고 시 대응 용이 | 여러 대의 서버를 운영할 때 세션 동기화·로드밸런싱 추가 작업 필요 |
3. JWT(Json Web Token) 기반 인증
- 저장 위치: 토큰 자체를 클라이언트(로컬스토리지 또는 쿠키)에 보관
- Stateless: 서버에는 토큰 검증을 위한 비밀 키만 있고, 별도 상태 저장소가 없음
| 장점 | 단점 |
| 서버 확장성(분산 시스템) 극대화: 모든 요청이 자체 토큰으로 인증 가능 | 토큰 탈취 시 만료 전까지 무단 사용 위험 |
| 서버 메모리 부하 거의 없음 | 토큰 무효화(로그아웃)는 별도의 블랙리스트 관리 필요 |
| 토큰 크기(헤더+페이로드+서명)가 커서 네트워크 부담 증가 |
다음 단계로는 JWT 기반 인증을 직접 구현해 보며 “Stateless 아키텍처” 의 이점을 체험할 예정입니다.
이 과정을 통해 두 방식의 장단점을 명확히 비교하고, 실제 서비스 요구에 따라 적재적소에 인증 방식을 선택할 수 있는 실력을 기르는 것이 목표입니다!
'프로젝트 회고' 카테고리의 다른 글
| 캐시 사용전략 : 왜 Redis인가? 캐시 설계부터 동기화 (2) | 2025.08.24 |
|---|---|
| 코드 refactor 과제: 리팩토링하며 생각했던 것들과 배운 것을 기록 (0) | 2025.07.04 |
| 아웃소싱 팀 프로젝트 회고: Docker로 프론트 연동 (0) | 2025.06.23 |
| JWT 인증 기반 뉴스피드 프로젝트: 회고 (1) | 2025.06.05 |
| JPA Schedule 프로젝트: 유지보수성과 학습을 위한 단계별 구현과정 (0) | 2025.05.23 |