Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- 순차탐색
- spring boot
- CS스터디
- 스터디2기
- cs
- 티스토리챌린지
- VI
- 터미널
- 오블완
- zsh theme
- Pager
- table status
- 분할정복 방법
- mysql 표
- 데이크스트라
- 이진탐색
- oh-my-zsh
- Less
- zsh
- 동적 프로그래밍 방법
- 인프런워밍업클럽
- MySQL
- 맥
- 알고리즘
- 오일러 경로
- 인프런
- 네트워킹데이
- mycli
- 욕심쟁이 방법
- mysql 표 출력
Archives
- Today
- Total
Develop
[Spring Boot] 게시판 - 05. Validation 검증 (입력값 유효성 검사) 본문
안녕하세요 .ᐟ
게시판 프로젝트 5일차입니다.
오늘은 사용자 입력값을 검증하는 Validation을 구현했습니다.
오늘의 학습 목표
- Validation이 필요한 이유
- @Valid와 Bean Validation 어노테이션
- DTO에 검증 로직 적용
- 검증 실패 시 에러 응답 처리
Validation을 왜 해야 할까?
// 기존 코드 - 검증 없음
@PostMapping
public ResponseEntity<PostResponse> createPost(@RequestBody PostCreateRequest request) {
return ResponseEntity.ok(postService.createPost(request, userId));
}
1. 잘못된 데이터가 서비스 레이어까지 도달함
- 빈 문자열, null 값이 그대로 DB에 저장될 수 있음
- 서비스 로직에서 NullPointerException 발생 가능
2. 보안 취약점
- 악의적인 사용자가 비정상적인 데이터를 전송할 수 있음
- SQL Injection, XSS 등의 공격에 노출
3. 데이터 무결성 훼손
- 이메일 형식이 아닌 값이 이메일 컬럼에 저장
- 너무 긴 문자열로 인한 DB 에러
목표: Controller 진입 시점에 검증
요청 → Controller(@Valid) → Service → Repository → DB
↓ 실패
400 Bad Request
(어떤 필드가 왜 틀렸는지 알려줌)
검증은 어디서 해야 할까?
| Controller | 입력값 형식 검증 | 빈 값, 이메일 형식, 문자열 길이 |
| Service | 비즈니스 규칙 검증 | 작성자 본인 확인, 중복 체크 |
| Repository | 데이터 무결성 | Unique 제약조건, FK 체크 |
이번에 구현할 것
- Controller 레벨: @Valid + Bean Validation 어노테이션
- Service 레벨: 비즈니스 규칙 (이미 4일차에 구현)
사용할 도구
Bean Validation 어노테이션
| @NotNull | null 불가 | 필수값 |
| @NotEmpty | null, 빈 문자열("") 불가 | 컬렉션, 문자열 |
| @NotBlank | null, 빈 문자열, 공백만 있는 문자열 불가 | 문자열 필수값 |
| @Size | 크기 제한 | 문자열 길이, 컬렉션 크기 |
| 이메일 형식 검증 | 이메일 필드 | |
| @Min / @Max | 숫자 범위 | 나이, 수량 |
| @Pattern | 정규표현식 검증 | 전화번호, 비밀번호 |
@NotNull vs @NotEmpty vs @NotBlank
String value = ""; // 빈 문자열
String value = " "; // 공백만 있는 문자열
String value = null; // null
@NotNull → null만 불가 (빈 문자열 허용)
@NotEmpty → null, 빈 문자열 불가 (공백만 있는 문자열 허용)
@NotBlank → null, 빈 문자열, 공백만 있는 문자열 모두 불가
결론: 문자열 필수값에는 @NotBlank를 사용하자!
구현하기
1. 의존성 확인
Spring Boot 3.x에서는 spring-boot-starter-validation이 필요합니다.
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
2. Request DTO에 검증 어노테이션 추가
UserCreateRequest
@Getter
@NoArgsConstructor
public class UserCreateRequest {
@NotBlank(message = "이메일은 필수입니다.")
@Email(message = "올바른 이메일 형식이 아닙니다.")
private String email;
@NotBlank(message = "비밀번호는 필수입니다.")
@Size(min = 8, max = 20, message = "비밀번호는 8~20자여야 합니다.")
private String password;
@NotBlank(message = "닉네임은 필수입니다.")
@Size(min = 2, max = 10, message = "닉네임은 2~10자여야 합니다.")
private String nickname;
}
3. Controller에 @Valid 적용
- @RequestBody 앞에 @Valid를 붙이면 자동으로 검증됩니다!
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody UserCreateRequest request) { // @Valid 추가!
return ResponseEntity.ok(userService.createUser(request));
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) { // @Valid 추가!
return ResponseEntity.ok(userService.updateUser(id, request));
}
}
4. 검증 실패 시 에러 처리 (GlobalExceptionHandler)
- 4일차에 구현한 GlobalExceptionHandler에 이미 추가되어 있습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
// @Valid 검증 실패 시 발생
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
List<ErrorResponse.FieldError> fieldErrors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> ErrorResponse.FieldError.builder()
.field(error.getField())
.value(error.getRejectedValue() != null ?
error.getRejectedValue().toString() : "")
.reason(error.getDefaultMessage())
.build())
.collect(Collectors.toList());
ErrorResponse response = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error(HttpStatus.BAD_REQUEST.getReasonPhrase())
.code("4000")
.message("잘못된 입력값입니다")
.fieldErrors(fieldErrors)
.build();
return ResponseEntity.badRequest().body(response);
}
}
검증 흐름 정리
1. 클라이언트가 POST /users 요청
2. Controller에서 @Valid가 UserCreateRequest 검증
- email이 빈 값? → MethodArgumentNotValidException 발생
- password가 7자? → MethodArgumentNotValidException 발생
3. GlobalExceptionHandler가 예외 catch
4. fieldErrors에 실패한 필드 정보 담아서 응답
Validation vs 비즈니스 예외 차이
| 발생 시점 | Controller 진입 전 | Service 로직 실행 중 |
| 예외 타입 | MethodArgumentNotValidException | BusinessException (커스텀) |
| 응답 코드 | 400 Bad Request | 상황에 따라 다름 (403, 404, 409 등) |
| fieldErrors | 포함 (어떤 필드가 틀렸는지) | 미포함 |
| 예시 | 빈 값, 형식 오류, 길이 초과 | 리소스 없음, 권한 없음, 중복 |
느낀 점
@Valid의 편리함
- Controller에 @Valid 하나만 붙이면 자동으로 검증됩니다.
- 검증 로직이 DTO에 모여있어 관리가 쉬웠습니다.
- Service 코드가 깔끔해졌습니다. (입력값 검증 코드 불필요)
명확한 에러 메시지의 중요성
- message 속성으로 사용자 친화적인 메시지를 제공할 수 있었습니다.
- 프론트엔드 와 협업 시 어떤 필드가 왜 틀렸는지 바로 알 수 있습니다.
검증 계층 분리
- 형식 검증 → Controller (Bean Validation)
- 비즈니스 검증 → Service (Custom Exception)
- 역할이 명확히 분리되어 코드가 깔끔해졌습니다.

'백엔드 > 게시판 만들기' 카테고리의 다른 글
| [Spring Boot] 게시판 - 07. 통합 테스트 작성하기 (MockMvc & Fixture 패턴) (1) | 2025.11.27 |
|---|---|
| [Spring Boot] 게시판 - 06. 단위 테스트 작성하기 (Mockito & AssertJ) (0) | 2025.11.24 |
| [Spring Boot] 게시판 - 04. 전역 예외 처리 (Global Exception Handler) (0) | 2025.11.22 |
| [Spring Boot] 게시판 - 03. 댓글(Comment) API 구현 (대댓글, 소프트 삭제) (0) | 2025.11.21 |
| [Spring Boot] 게시판 - 02. Post API 개발 (페이징 처리, IntelliJ HTTP Client) (0) | 2025.11.20 |