Repository navigation
chore: Docker MySQL 8.4 개발 환경 및 테스트 설정 구성 - #10
Conversation
HAHEONHWI
left a comment
There was a problem hiding this comment.
리뷰 요약
Docker MySQL 구성, .env 분리, H2 테스트 프로필 분리, 회원가입 API와 공통 응답/예외 구조까지 전반적으로 깔끔합니다. DB unique 제약으로 동시 가입 race condition까지 처리한 점도 좋습니다.
수정 권장
GlobalExceptionHandler의Exceptioncatch-all이 404(NoResourceFoundException), 415, 400(파라미터 누락) 같은 Spring MVC 예외까지 잡아서 500으로 응답함 (inline 참고)- 이메일 정규화(trim/lowercase)가 없어서 대소문자만 다른 이메일이 DB collation에 따라 중복 가입될 수 있음
제안 (선택)
- 회원가입 성공 응답을
201 Created로 global/exception이domain.user.exception을 import하는 구조 →BusinessException(ErrorCode)하나로 통합하면 도메인이 늘어나도 handler를 수정할 필요가 없음- 비밀번호 확인 검증을 DB 조회보다 먼저 수행
ddl-auto: update는 local 전용으로 유지하고, 운영 전에는 Flyway 등 migration 도구 도입 권장
Generated by Claude Code
| return createErrorResponse(ErrorCode.VALIDATION_ERROR, details); | ||
| } | ||
|
|
||
| @ExceptionHandler(Exception.class) |
There was a problem hiding this comment.
Exception.class catch-all이 Spring MVC 내부 예외까지 잡습니다. 예를 들어 없는 URL을 호출하면 NoResourceFoundException이 404가 아니라 500 + error 로그로 처리됩니다. MissingServletRequestParameterException, HttpMediaTypeNotSupportedException도 마찬가지입니다.
권장: extends ResponseEntityExceptionHandler로 MVC 예외의 기본 상태코드를 유지하거나, 최소한 ErrorResponse를 구현한 예외는 해당 getStatusCode()를 그대로 쓰도록 분기해 주세요.
Generated by Claude Code
There was a problem hiding this comment.
ResponseEntityExceptionHandler를 상속하도록 수정함, Spring MVC 내부 예외는 기본 HTTP 상태 코드를 유지하도록 변경
기존 예외 처리 로직도 오버라이딩하여 BABI의 공통 응답 형식을 유지
테스트를 작성해 400, 404, 405, 415, 500 응답을 검증및 ./gradlew clean build 결과 전체 45개 테스트가 통과했습니다.
| } | ||
| } | ||
| private void validateSignup(SignupRequest request) { | ||
| if (userRepository.existsByEmail(request.email())) { |
There was a problem hiding this comment.
이메일 정규화가 없습니다. MySQL의 기본 collation(utf8mb4_0900_ai_ci)은 대소문자를 구분하지 않아 A@x.com과 a@x.com을 중복으로 막지만, H2 테스트 환경이나 collation이 바뀐 환경에서는 둘 다 가입됩니다. 로그인 시 매칭도 같은 문제가 생깁니다.
request.email().trim().toLowerCase(Locale.ROOT)로 정규화한 값을 중복 체크와 저장 양쪽에 사용하는 걸 권장합니다.
덤으로: password/passwordConfirm 비교는 DB 조회가 필요 없으니 existsByEmail보다 먼저 하면 불필요한 쿼리를 줄일 수 있습니다.
Generated by Claude Code
There was a problem hiding this comment.
trim().toLowerCase(Locale.ROOT)를 적용해 이메일을 정규화및 중복 검사와 DB 저장 시 동일한 값을 사용하도록 수정
비밀번호 일치 여부를 이메일 중복 조회보다 먼저 검사하도록 순서를 변경
| while (cause != null) { | ||
| if (cause instanceof ConstraintViolationException violation) { | ||
| String name = violation.getConstraintName(); | ||
| if ("email".equals(name) |
There was a problem hiding this comment.
제약조건 이름을 문자열로 비교하는 방식은 DB 벤더와 드라이버 메시지에 의존해서 깨지기 쉽습니다. 현재는 @UniqueConstraint(name = "email")과 MySQL의 users.email에 맞춰져 있어 동작은 할 것 같지만, 제약 이름을 uk_users_email처럼 명시적으로 바꾸고 이 상수 하나와 비교하는 편이 안전합니다.
Generated by Claude Code
There was a problem hiding this comment.
이메일 UNIQUE 제약조건 이름을 uk_users_email로 명시하고, User 엔티티의 상수로 관리하도록 수정
기존 MySQL DB의 email 인덱스와도 호환되도록 처리했으며, MySQL과 H2에서 반환하는 제약조건 이름을 구분하여 비교하도록 개선
| } | ||
|
|
||
| @PostMapping("/signup") | ||
| public ApiResponse<SignupResponse> signup(@Valid @RequestBody SignupRequest request) { |
There was a problem hiding this comment.
nit: 리소스 생성이니 @ResponseStatus(HttpStatus.CREATED)(또는 ResponseEntity.status(201)) 사용을 권장합니다. 프론트와 응답 코드 합의가 이미 돼 있다면 무시해도 됩니다.
Generated by Claude Code
There was a problem hiding this comment.
이메일 UNIQUE 제약조건 이름을 uk_users_email로 명시 및 User 엔티티의 상수로 관리하도록 수정
기존 MySQL DB의 email 인덱스와도 호환되도록 처리 및MySQL과 H2에서 반환하는 제약조건 이름을 구분하여 비교하도록 개선
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
HAHEONHWI
left a comment
There was a problem hiding this comment.
재리뷰 (e5848b8)
지난 리뷰 반영 확인했습니다. 꼼꼼하게 반영해 주셔서 감사합니다.
- ✅
ResponseEntityExceptionHandler상속 → 404/405/415가 500으로 떨어지던 문제 해결 - ✅ 이메일 정규화(trim + lowercase) 및 중복 검사/저장 값 일치
- ✅ 비밀번호 확인을 DB 조회보다 먼저 검사
- ✅ 제약조건 이름
uk_users_email상수화 - ✅ 회원가입
201 Created
남은 것
- 에러 응답 형식 불일치: override한 3개(validation, body 파싱, 405)만
ApiResponse형식이고, 나머지 MVC 예외(404, 415, 파라미터 누락 400 등)는 기본ProblemDetail형식으로 나갑니다. 테스트도 이 케이스들은 status만 검증하고 있어서 드러나지 않았습니다.handleExceptionInternal하나를 override하면 전부 같은 형식으로 통일할 수 있습니다. (inline 참고) - checked exception 누락: catch-all이
RuntimeException으로 좁혀져서, checked exception은 Spring Boot 기본/error응답으로 나갑니다. 이제 MVC 예외는 부모 클래스가 더 구체적인 타입으로 먼저 처리하니까, 다시Exception.class로 넓혀도 안전합니다. - nit:
matchesEmailConstraint가 벤더별 이름 파싱 때문에 꽤 복잡해졌습니다. 당장은 괜찮고, 로컬 DB를 한 번씩 초기화해서 legacyemail인덱스가 사라지면LEGACY_EMAIL_UNIQUE_CONSTRAINT분기는 정리해도 될 것 같습니다.
1, 2번만 반영되면 approve 가능할 것 같습니다.
Generated by Claude Code
| import java.util.List; | ||
|
|
||
| @RestControllerAdvice | ||
| public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { |
There was a problem hiding this comment.
override하지 않은 MVC 예외(NoResourceFoundException, HttpMediaTypeNotSupportedException, MissingServletRequestParameterException 등)는 부모의 기본 구현을 타서 ProblemDetail 형식(type, title, status, detail)으로 응답합니다. 우리 ApiResponse 형식과 달라서 프론트에서 에러 처리가 갈립니다.
모든 MVC 예외가 최종적으로 거치는 handleExceptionInternal을 override하면 한 곳에서 통일할 수 있습니다.
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, Object body, HttpHeaders headers,
HttpStatusCode statusCode, WebRequest request) {
ErrorCode code = /* statusCode 기준 매핑 (404 → NOT_FOUND 등) */;
return ResponseEntity.status(statusCode).headers(headers)
.body(createErrorResponseBody(code, List.of()));
}테스트의 404/415/400 케이스에도 jsonPath("$.error.code") 검증을 추가해 주세요.
Generated by Claude Code
| .body(createErrorResponseBody(ErrorCode.METHOD_NOT_ALLOWED, List.of())); | ||
| } | ||
|
|
||
| @ExceptionHandler(RuntimeException.class) |
There was a problem hiding this comment.
RuntimeException으로 좁히면 checked exception은 여기서 안 잡히고 Spring Boot 기본 에러 응답으로 나갑니다.
지금은 ResponseEntityExceptionHandler가 MVC 예외를 더 구체적인 타입으로 먼저 처리하기 때문에(Spring은 가장 가까운 타입의 handler를 고름), Exception.class로 다시 넓혀도 404/405가 500으로 떨어지지 않습니다.
Generated by Claude Code
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
HAHEONHWI
left a comment
There was a problem hiding this comment.
본 리뷰는 AI를 활용하여 초안을 검토한 내용입니다.
판정: 비차단 의견 반영 시 승인 가능합니다.
- compose.yml: .env 미작성 시 빈 값으로 기동될 수 있으므로
${MYSQL_PASSWORD:?required}적용을 권장합니다. - application-local.yml: ddl-auto update 사용 시 제약명 변경이 기존 로컬 DB에 반영되지 않을 수 있으므로 볼륨 재생성 안내를 README에 명시해 주시기 바랍니다.
- UserService.matchesEmailConstraint: 제약명 문자열 파싱이 DB 메시지 형식에 의존하므로 MySQL 8.4 환경에서 중복 가입 통합 확인 후 결과 공유를 권장합니다.
- 불필요한 주석 정리를 권장합니다. (비차단)
- 이메일 정규화 로직 한 곳으로 통일을 권장합니다. (비차단)
검증 결과: 변경 파일 diff를 전체 열람하였고 compose 포트가 127.0.0.1로 한정되며 .env.example에 비밀값이 없음을 확인하였습니다. 빌드 및 테스트는 실행하지 못하였습니다.
작업 개요
팀원 간 데이터베이스 개발 환경을 통일하기 위해
Docker Compose 기반 MySQL 8.4 LTS 환경을 구성했습니다.
주요 변경 사항
테스트 결과