📙 목차
- Validation
- BindingResult
- Bean Validation
- Bean Validation 사용 예제 1 - 기본 흐름
- Bean Validation 사용 예제 2 - 글로벌 예외 처리
- Bean Validation 사용 예제 3 - 그룹별 검증 조건(groups)
1. Validation
Validation - 검증
- 특정 데이터(주로 클라이언트의 요청 데이터)의 값이 유효한지 확인하는 절차
- 잘못된 데이터의 유입을 방지하여 시스템의 신뢰성과 안정성을 확보하는 핵심 과정이다.
- Controller의 주요한 역할 중 하나는 Validation 이다. HTTP 요청이 정상인지 검증한다.
Validation의 역할
- 사용자에게 입력 오류에 대한 명확한 피드백을 제공한다.
- 오류 발생 시에도 시스템이 정상적으로 동작하도록 보호한다.
- 사용자가 입력한 데이터를 보존하여 재입력의 불편을 줄인다.
Validation의 종류
- 프론트엔드 검증
- 사용자 인터페이스에서 입력값을 즉시 검사하여 빠른 피드백을 제공하는 검증이다.
- 사용자 경험 향상에 도움을 주지만, 클라이언트 조작이 가능하여 보안에 취약하다.
- 서버 검증
- 서버에서 모든 입력 데이터를 재검증하는 검증이다.
- 보안상 반드시 수행해야 하며, API 명세서에 검증 규칙과 오류 응답을 명확히 정의하는 것이 중요하다.
- 데이터베이스 검증
- NOT NULL, UNIQUE, CHECK 제약조건 등 데이터 무결성을 보장하는 최종 방어선
- 애플리케이션 차원의 검증을 우회하는 경우에도 데이터 일관성을 유지한다.
2. BindingResult
BindingResult
- 스프링(Spring) MVC에서 주로 폼 데이터를 검증하고 바인딩할 때 사용하는 인터페이스
- 클라이언트가 전송한 폼 데이터(요청 파라미터)를 컨트롤러 메서드의 객체에 바인딩(맵핑)할 때 발생하는 바인딩 오류와 검증 오류를 담는 객체이다.
- @Valid 또는 @Validated 어노테이션과 함께 사용하여, 데이터 검증 결과를 담고 검증 실패 시 적절한 처리를 가능하게 한다.
- 보통 @ModelAttribute나 @RequestBody로 바인딩된 객체 바로 다음 파라미터로 선언한다.
@PostMapping("/user")
public String createUser(@Valid @ModelAttribute UserForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 검증 실패 시 다시 입력 폼으로 이동
return "userForm";
}
// 검증 성공 시 다음 로직 실행
userService.save(form);
return "redirect:/success";
}
BindingResult 의 역할
- 폼 데이터 바인딩 시 발생한 타입 변환 오류 저장
- 검증기(Validator)가 실행한 검증 결과 저장
- 오류가 있을 때 이를 확인하고 사용자에게 피드백 제공
3. Bean Validation
Bean Validation
- Java 객체의 필드나 속성에 제약 조건을 선언하고, 이를 자동으로 검증할 수 있도록 도와주는 표준 검증 프레임워크
- JSR-303, JSR-380 스펙에 기반한 Java의 표준 Validation API
- 어노테이션 기반으로 객체의 필드에 제약 조건을 선언한다.
- 런타임 시 자동으로 유효성 검사를 수행한다.
- DTO 검증, 폼 입력값 검증 등에 자주 사용된다.
- 대표적인 구현체: Hibernate Validator
// 사용 예시
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDTO {
@NotNull // null이면 안 됨
private String name;
@Size(min = 6, max = 20) // 문자열의 길이 제약
private String password;
}
// 검증 수행 방법
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody @Valid UserDTO user) {
// 유효하지 않으면 자동으로 400 Bad Request 반환
return ResponseEntity.ok().build();
}
대표 어노테이션
어노테이션 | 설명 |
@NotNull | null이 아니어야 함 |
@NotEmpty | null 또는 빈 문자열은 안 됨 |
@NotBlank | null, 빈 문자열, 공백만 있는 문자열 안 됨 |
@Size(min, max) | 크기(문자열, 컬렉션 등) 제약 |
이메일 형식 검증 | |
@Pattern | 정규표현식 패턴 매칭 |
@Min, @Max | 숫자의 최소/최대 값 지정 |
@Positive, @Negative | 양수 / 음수 검증 |
@Valid, @Validated
- Bean Validation을 적용할 때 사용하는 어노테이션
구분 | @Valid (javax.validation) | @Validated (Spring Framework) |
소속 패키지 | javax.validation.Valid 또는 jakarta.validation.Valid | org.springframework.validation.annotation.Validated |
기본 기능 | 단순히 Bean Validation 수행 | Bean Validation 수행 + 그룹(Group) 지정 가능 |
그룹 지정 | 불가능 (기본 그룹만 검증) | 가능 (@Validated(Group.class)) |
스프링 지원 | 스프링 MVC, 스프링 부트에서 기본 지원 | 스프링 전용 기능 |
사용 위치 | 주로 @RequestBody, 메서드 파라미터 등에서 검증 시 사용 | 컨트롤러, 서비스 등에서 그룹별 검증 시 사용 |
추가 기능 | 없음 | AOP 기반으로 클래스 레벨 검증 등도 가능 |
BindingResult 한계와 Bean Validation의 주요 특징
BindingResult 한계 | Bean Validation의 주요 특징 |
컨트롤러에만 사용 가능 | 도메인 객체에 선언적 제약 → 어디서든 재사용 가능 |
검증 코드와 비즈니스 로직 혼재 | 선언형 어노테이션으로 관심사 분리 |
응답 포맷 직접 구성 필요 | 예외 기반 전역 처리로 일관된 오류 응답 제공 |
스프링에 종속적 (표준 아님) | JSR 303/380 표준 → 다양한 프레임워크와 호환 가능 |
@Valid + BindingResult vs @Valid
항목 | @Valid + BindingResult | @Valid |
검증 실패 시 동작 | BindingResult에 오류 저장 (예외 발생 X) | MethodArgumentNotValidException 발생 |
예외 발생 여부 | ❌ 예외 발생하지 않음 | ✅ 예외 발생 (스프링 예외 처리로 감) |
오류 처리 위치 | 컨트롤러 메서드 내부에서 직접 처리 가능 | 전역 예외 처리기 또는 기본 에러 응답 |
사용 추천 케이스 | HTML 폼, 유효성 실패 시 다시 폼 보여줄 때 | REST API에서 일괄적으로 오류 처리할 때 |
4. Bean Validation 사용 예제 1 - 기본 흐름
DTO 정의 → Controller 검증 → 오류 처리
1) DTO 클래스
- @NotBlank, @Email, @Size 등 Bean Validation 어노테이션을 붙여 검증 조건 선언
public class UserDTO {
@NotBlank(message = "이름은 필수입니다.")
private String name;
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
@Size(min = 6, message = "비밀번호는 최소 6자 이상이어야 합니다.")
private String password;
}
2) Controller
- @RequestBody @Valid를 사용해 DTO 검증 수행
- 검증 실패 시 Spring Boot가 자동으로 400 에러와 기본 JSON 오류 메시지를 응답
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> register(@RequestBody @Valid UserDTO userDTO) {
// 검증 실패 시 Spring이 자동으로 400 에러를 발생시키고 메시지 반환
return ResponseEntity.status(HttpStatus.CREATED).body("회원가입 성공");
}
}
3) 검증 실패 시 응답
{
"timestamp": "2025-05-15Txx:xx:xx.xxx+00:00",
"status": 400,
"errors": [
"name: 이름은 필수입니다.",
"email: 이메일 형식이 올바르지 않습니다.",
"password: 비밀번호는 최소 6자 이상이어야 합니다."
],
"path": "/users"
}
5. Bean Validation 사용 예제 2 - 글로벌 예외 처리
Spring Boot는 기본적으로 @Valid 검증 실패 시 400 에러와 기본 오류 메시지를 반환하지만,
사용자 정의 형식으로 에러 메시지를 통일하거나 추가 정보를 담고 싶을 때 글로벌 예외 처리기를 구현할 수 있다.
1) 글로벌 예외 처리기 클래스 생성
- @RestControllerAdvice가 모든 컨트롤러의 예외를 감지한다.
- @ExceptionHandler(MethodArgumentNotValidException.class)로 Bean Validation 실패 예외를 잡는다.
- 필드명과 메시지를 Map으로 만들어 클라이언트에게 JSON 형태로 반환한다.
- 원하는 포맷으로 메시지를 통일하거나 추가 정보를 담기 쉽다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String message = error.getDefaultMessage();
errors.put(fieldName, message);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
}
2) DTO, Controller 는 기본 흐름과 동일하게 작성
3) 검증 실패 시 응답
{
"name": "이름은 필수입니다.",
"email": "이메일 형식이 올바르지 않습니다.",
"password": "비밀번호는 최소 6자 이상이어야 합니다."
}
6. Bean Validation 사용 예제 3 - 그룹별 검증 조건(groups)
1) 그룹 인터페이스 선언
public interface CreateGroup {}
public interface UpdateGroup {}
2) DTO 클래스에 그룹별 검증 조건 지정
- groups 속성에 그룹 인터페이스를 지정하여 특정 상황에 맞는 검증 조건을 분리한다.
- 공통 검증은 그룹 지정 없이 @Size처럼 기본 그룹으로 설정 가능하다.
public class UserDTO {
// 회원가입 시 필수, 수정 시 무시
@NotBlank(message = "이름은 필수입니다.", groups = CreateGroup.class)
private String name;
// 회원가입 시 필수, 수정 시 무시
@Email(message = "이메일 형식이 올바르지 않습니다.", groups = CreateGroup.class)
private String email;
// 수정 시 필수, 회원가입 시 무시
@NotBlank(message = "아이디는 필수입니다.", groups = UpdateGroup.class)
private String id;
// 회원가입, 수정 공통 (groups 지정 없으면 기본 그룹)
@Size(min = 6, message = "비밀번호는 최소 6자 이상이어야 합니다.")
private String password;
}
3) Controller에서 그룹 지정하여 검증 수행
- @Validated(그룹.class)로 검증 대상 그룹을 지정한다. (@Valid는 그룹 지정 불가)
@RestController
@RequestMapping("/users")
public class UserController {
// 회원가입: CreateGroup 검증
@PostMapping
public String createUser(@RequestBody @Validated(CreateGroup.class) UserDTO userDTO) {
return "회원가입 성공";
}
// 수정: UpdateGroup 검증
@PutMapping("/{id}")
public String updateUser(@PathVariable String id, @RequestBody @Validated(UpdateGroup.class) UserDTO userDTO) {
return "회원 수정 성공";
}
}
* groups 방식 vs DTO 분리 방식
구분 | groups 방식 | DTO 분리 방식 |
검증 관리 방식 | 하나 DTO에 그룹별 검증 어노테이션 분리 | 검증 시나리오별 DTO 클래스 분리 |
코드량 | 적음 | 많음 |
유지보수성 | 그룹이 많아질수록 복잡해질 수 있음 | 명확하고 직관적 |
사용 용도 | 비슷한 검증 대상에서 세밀한 검증 제어 필요 | 완전히 다른 검증 규칙 및 역할 구분 필요 |
실무에서는 등록, 수정 등 상황별로 DTO 클래스를 따로 만들어 관리하는 것이 권장된다.
'Spring > 강의' 카테고리의 다른 글
[📙 숙련 Spring] 2-2. Cookie, Session (2) | 2025.05.16 |
---|---|
[📙 숙련 Spring] 2-1. 인증과 인가 (0) | 2025.05.16 |
[📙 숙련 Spring] 1-2. Spring Bean 등록 (0) | 2025.05.15 |
[📙 숙련 Spring] 1-1. 객체 지향과 Spring 핵심 개념 (0) | 2025.05.15 |
[📗 스프링 입문] 2. 스프링 웹 개발 기초 (1) | 2025.05.08 |