이 글은 '스프링 부트 핵심 가이드 - 스프링 부트를 활용한 애플리케이션 개발 실무' 책을 통해 학습한 내용을 정리한 글입니다.
10장. 유효성 검사와 예외 처리
Bean Validation
- 어노테이션을 통해 데이터를 검증하는 기능을 제공한다.
- 유효성 검사를 위한 로직을 DTO 같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행한다.
- 코드의 간결함을 유지할 수 있고, 가독성이 좋아진다.
Hibernate Validator
- Bean Validation 명세의 구현체
- 스프링 부트에서 채택하여 사용하고 있다.
- JSR-303 명세의 구현체로서 도메인 모델에서 어노테이션을 통한 필드값 검증을 가능하도록 도와준다.
의존성 추가
- gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
- maven
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
스프링 부트의 유효성 검사
각 계층으로 데이터가 넘어오는 시점에 해당 데이터에 대한 검사를 수행한다. 일반적으로 DTO 객체를 대상으로 유효성 검사를 수행한다.
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class UserSignUpDto {
@NotBlank(message = "이메일은 필수 항목 입니다.")
@EmailValidation(message = "이메일 형식에 맞게 입력해주세요.")
private String email;
@NotBlank(message = "이름은 필수 항목 입니다.")
private String name;
@NotBlank(message = "비밀번호는 필수 항목 입니다.")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{6,}$",
message = "비밀번호는 알파벳, 숫자, 특수문자를 각각 하나 이상 포함하여 6자 이상으로 설정해주세요.")
private String password;
@NotBlank(message = "연락처는 필수 항목 입니다.")
@Pattern(regexp = "\\d{3}-\\d{3,4}-\\d{4}",
message = "올바른 형식으로 연락처를 입력해주세요. 01X-XXX(X)-XXXX")
private String phone;
private UserType userType;
}
유효성 검사를 위한 대표적인 어노테이션
- 문자열 검증
- @Null : null 값만 허용
- @NotNull : null 값 불허, "", " " 허용
- @NotEmpty : null 값, "" 불허, " " 허용
- @NotBlank : null 값, "", " " 불허
- 최댓값/최솟값 검증
- BigDecimal, BigInteger, int, long 타입 지원
- @DecimalMax(value = "$numberString") : $numberString보다 작은 값 허용
- @DecimalMin(value = "$numberString") : $numberString보다 큰 값 허용
- @Min(value = $number) : $number 이상의 값 허용
- @Max(value = $number) : $number 이하의 값 허용
- 값의 범위 검증
- BigDecimal, BigInteger, int, long 타입 지원
- @Positive : 양수 허용
- @PositiveOrZero : 0을 포함한 양수 허용
- @Negative : 음수 허용
- @NegativeOrZero : 0을 포함한 음수 허용
- 시간에 대한 검증
- Date, LocalDate, LocalDateTime 등의 타입 지원
- @Future : 현재보다 미래의 날짜 허용
- @FutureOrPresent : 현재를 포함한 미래의 날짜 허용
- @Past : 현재보다 과거의 날짜 허용
- @PastOrPresent : 현재를 포함한 과거의 날짜 허용
- 이메일 검증
- @Email : 이메일 형식 검사, "" 허용 → 그러나 @가 없어진 것만 인식하고, @ 뒤의 형식은 검사할 수 없다.
- @Email : 이메일 형식 검사, "" 허용 → 그러나 @가 없어진 것만 인식하고, @ 뒤의 형식은 검사할 수 없다.
- 자릿수 범위 검증
- BigDecimal, BigInteger, int, long 타입 지원
- @Digits(integer = $number1, fraction = $number2) : $number1의 정수 자릿수와 $number2의 소수 자릿수 허용
- Boolean 검증
- @AssertTrue : true인지 체크, null 값 체크 X
- @AssertFalse : falser인지 체크, null 값 체크 X
- 문자열 길이 검증
- @Size(min = $number1, max = $number2) : $number1 이상 $number2 이하의 범위 허용
- @Size(min = $number1, max = $number2) : $number1 이상 $number2 이하의 범위 허용
- 정규식 검증
- @Pattern(regexp = "$expression") : 정규식 검사, java.util.regex.Pattern 패키지의 컨벤션을 따른다.
Controller 에서의 유효성 검사
- @Valid
- 자바에서 지원하는 어노테이션
- @Valid 어노테이션을 지정해야 DTO 객체에 대해 유효성 검사를 수행한다.
@PostMapping("/signUp")
public ResponseEntity<?> signUpUser(
@RequestBody @Valid UserSignUpDto userSignUpDto,
Errors errors
) { ...
}
규칙에 맞게 요청하면 http 응답으로 200 OK 메세지가 뜨고, 아닌 경우 400 BAD REQUEST 가 뜬다.
- @Validated
- 스프링에서 지원하는 어노테이션
- @Valid 어노테이션의 기능을 포함하고 있다.
- 유효성 검사를 그룹으로 묶어 대상을 턱정할 수 있는 기능이 있다.
- Custom Validation
- 자바 또는 스프링의 어노테이션에서 제공하지 않는 기능을 유효성 검사에 사용해야 하는 경우
- 아래 코드와 같이 ConstraintValidator 과 커스텀 어노테이션을 조합해 별도의 유효성 검사 어노테이션을 생성할 수 있다.
@NotBlank(message = "이메일은 필수 항목 입니다.")
@EmailValidation(message = "이메일 형식에 맞게 입력해주세요.")
private String email;
public class EmailValidator implements ConstraintValidator<EmailValidation, String> {
private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
private static final String EMAIL_DOMAIN = ".com";
@Override
public void initialize(EmailValidation constraintAnnotation) {
}
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
...
}
}
@Documented
@Constraint(validatedBy = EmailValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EmailValidation {
String message() default "이메일 형식에 맞게 입력해주세요.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
예외 처리
예외 (Exception)
입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황이다. 예외는 개발자가 직접 처리할 수 있으므로 미리 코드 설계를 통해 처리할 수 있다.
에러 (Error)
주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없다. 대표적인 예로 OutOfMemory, StackOverFlow 등이 있다. 이러한 에러는 미리 문제가 발생하지 않도록 예방해서 원천적으로 차단해야 한다.
예외 처리 방법
- 예외 복구
- 예외 상황을 파악해서 문제를 해결하는 방식
- try-catch 구문 활용
- 예외 처리 회피
- 예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식
- throw 키워드 사용
- 예외 전환
- try-catch 방식을 사용하면서 catch 블록에서 throw 키워드를 사용하여 다른 예외 타입으로 전달하는 방식
- 커스텀 예외를 만드는 과정에서 사용되는 방법
스프링 부트의 예외 처리 방식
- @(Rest)ControllerAdvice 와 @ExceptionHandler 를 통해 모든 컬트롤러의 예외 처리
- @ControllerAdvice 대신 @RestControllerAdvice 를 사용하면 결괏값을 JSON 형태로 반환할 수 있다.
- @ExceptionHandler 를 통해 특정 컨트롤러의 예외 처리
@RestControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<?> handleException(RuntimeException e, HttpServletRequest request) {
...
}
}
위 코드 처럼 클래스에 @RestControllerAdvice를 사용하면 @Controller 나 @RestController 에서 발생하는 예외를 한 클래스에서 관리하고 처리할 수 있으며, 패키지를 지정해서 범위를 지정할 수도 있다. 그리고 @ExceptionHandler에 처리하고 싶은 예외를 value 로 지정해주면 지정한 예외를 처리하는 메서드를 생성할 수 있다.
커스텀 예외
- 네이밍에 개발자의 의도를 담을 수 있기 때문에 이름만으로도 어느 정도 예외 상황을 짐작할 수 있다.
- 애플리케이션에서 발생하는 예외를 개발자가 직접 관리하기 수월해 진다.
- 예외 상황에 대한 처리도 용이하다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CustomException extends RuntimeException {
private ErrorCode errorCode;
private String message;
private HttpStatus httpStatus;
public CustomException(ErrorCode errorCode) {
this.errorCode = errorCode;
this.message = errorCode.getMessage();
this.httpStatus = errorCode.getHttpStatus();
}
}
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
ALREADY_EXISTS_EMAIL("이미 가입된 이메일입니다.", BAD_REQUEST),
...
;
private final String message;
private final HttpStatus httpStatus;
}
'북 스터디 > 스프링 부트 핵심 가이드' 카테고리의 다른 글
[스프링부트] 13장. 서비스의 인증과 권한 부여 (0) | 2023.07.09 |
---|---|
[스프링 부트] 11장. 액추에이터 활용하기 ~ 12장. 서버 간 통신 (0) | 2023.07.02 |
[스프링 부트] 09장. 연관관계 매핑 (0) | 2023.06.18 |
[스프링 부트] 08장. Spring Data JPA 활용 (0) | 2023.06.11 |
[스프링 부트] 06장. 데이터베이스 연동 (0) | 2023.06.04 |