스프링 핵심가이드) 북스터디 6주차 : 10장 유효성 검사와 예외처리
10 유효성 검사와 예외처리
유효성 검사 / 데이터 검증 : 비즈니스 로직이 올바르게 동작하기 위해 데이터를 사전 검증하는 작업
10.1 일반적인 애플리케이션 유효성 검사의 문제점
일반적으로 사용되는 데이터 검증 로직의 문제점 :
1. 계층별로 진행하는 유효성 검사는 검증 로직이 각 클래스 별로 분산되어 있어 관리가 어려움.
2. 검증로직에 중복이 많아 여러 곳에 유사한 기능의 코드가 존재할 수 있음.
3. 검증해야 할 값이 많으면 검증하는 코드가 길어져 가독성이 떨어짐.
=> Bean Validation이라는 데이터 유효성 검사 프레임워크로 위의 문제들을 해결.
Bean Validation은 어노테이션을 통해 다양한 데이터를 검증하는 기능을 제공.
- 유효성 검사를 위한 로직을 DTO같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행.
- 어노테이션을 사용한 검증방식이라 코드의 간결함도 유지할 수 있음.
10.2 Hibernate Validator
- Bean Validation 명세의 구현체. 스프링 부트에서 Hibernate Validator를 유효성 검사 표준으로 채택해 사용.
- 도메인 모델에서 어노테이션을 통한 필드값 검증을 가능하게 도와줌.
10.3 스프링 부트에서의 유효성 검사
1. 의존성 추가
build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-validation' 추가
2. 유효성 검사는 각 계층으로 데이터가 넘어오는 시점에 해당 데이터에 대한 검사를 실시함.
따라서 DTO 객체의 필드들에 어노테이션으로 검증
- 문자열 검증
· @Null : null 값만 허용
· @NotNull : null 허용하지 않음. "", " "는 허용
· @NotEmpty : null, ""는 허용하지 않음. " "는 허용
· @NotBlank : null, "", " " 허용하지 않음.
- 최대/최소값 검증
· BigDecimal, BigInteger, int, long 타입 지원
· @DemicalMax(value = "$numberString") : $numberString보다 작은 값을 허용
· @DemicalMin(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 : 이메일 형식을 검사. ""는 허용
- 자릿수 범위 검증
· BigDemical, BigInteger, int, long 등의 타입 지원.
· @Digits(integer = $number1, fraction = $number2) : $number1의 정수 자릿수와 $number2의 소수 자릿수를 허용합니다.
- Boolean 검증
· @AssertTrue : true인지 체크. null 값은 체크하지 않음.
· @AssertFalse : false인지 체크. null 값은 체크하지 않음.
- 문자열 길이 검증
· @Size(min = $number1, max = $number2) : $number1 이상 $number2 이하의 범위 허용
- 정규식 검증
· @Pattern(regexp = "$expression") : 정규식을 검사. 정규식은 자바의 java.util.regec.Pattern 패키지의 컨벤션을 따름.
@Valid 어노테이션을 지정해야 DTO 객체에 대해 유효성 검사를 수행할 수 있음.
- 정규식 : 특정한 규칙을 가진 문자열 집합을 표현하기 위해 쓰이는 형식.
전화번호, 주민등록번호, 이메일과 같이 특정형식의 값을 검증해야 할 때가 있다. 이런 값은 정규식을 통해 쉽게 검증할 수 있다.
- 정규식에서 사용하는 요소
^ : 문자열의 시작
$ : 문자열의 종료
. : 임의의 한 문자
* : 앞 문자가 없거나 무한정 많음
+ : 앞 문자가 하나 이상
? : 앞 문자가 없거나 하나 존재
[,] : 문자의 집합이나 범위를 나타내며, 두 문자 사이는 - 기호로 범위를 표현
{, } : 횟수 또는 범위를 의미
(, ) : 괄호 안의 문자를 하나의 문자로 인식
| : 패턴 안에서 OR 연산을 수행
\ : 정규식에서 역슬래시는 확장문자로 취급, 역슬래시 다음에 특수문자가 오면 문자로 인식
\b : 단어의 경계
\B : 단어가 아닌 것에 대한 경계
\A : 입력이 시작 부분
\G : 이전 매치의 끝
\Z : 종결자가 있는 경우 입력의 끝
\z : 입력의 끝
\s : 공백 문자
\S : 공백 문자가 아닌 나머지 문자(^\s와 동일)
\w : 알파벳이나 숫자
\W : 알파벳이나 숫자가 아닌 문자(^/w와 동일)
\d : 숫자 [0 - 9] 와 동일하게 취급
\D : 숫자를 제외한 모든 문자 (^0-9)와 동일
정규식 연습 사이트
https://regexr.com/
https://regex101.com/
10.3.4 @Validated 활용
@Valid는 자바에서 지원하는 어노테이션.
스프링에서도 @Validated라는 별도의 어노테이션을 지원함.
@Validated는 @Valid의 기능을 포함하고 있기 때문에 @Validate로 변경할 수 있고
@Validated는 유효성 검사를 그룹으로 묶어 대상을 특정할 수 있는 기능이 있음
- @Validated 어노테이션에 특정 그룹을 설정하지 않은 경우에는 groups가 설정되지 않은 필드에 대해 유효성 검사를 수행.
- @Validated 어노테이션에 특정 그룹을 설정하는 경우에는 지정된 그룹으로 설정된 필드에 대해서만 유효성 검사를 수행.
커스텀 Validation을 만들 수도 있음.
10.4 예외처리
10.4.1 예외와 에러
예외 : 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못 하는 상황.
- 개발자가 직접 처리할 수 있는 것이므로 미리 코드 설계를 통해 처리할 수 있음.
에러 : 자바 가상머신에서 주로 발생시키는 것으로 애플리케이션 코드에서 처리할 수 있는 것이 거의 없음.
- ex) 메모리 부족(OutOfMemory), 스택 오버플로우(StackOverFlow)가 있음. 이런한 에러는 예방해서 원칙적으로 차단해야함.
10.4.2 예외 클래스
모든 예외 클래스는 Throwable 클래스를 상속받음. Exception은 다양한 자식클래스를 가지는데 Checked Exception과 Unchecked Exception으로 구분할 수 있음.
Checked Exception | Unchecked Exception | |
처리 여부 | 반드시 예외처리 필요 | 명시적 처리를 강제하지 않음 |
확인 시점 | 컴파일 단계 | 실행 단계 |
대표적인 예외 클래스 | IOException SQLException |
RuntimeException NullPointerException IllegalArgumentException IndexOutOfBoundException SystemException |
- Checked Exception : 컴파일 단계에서 확인 가능한 예외 상황. IDE에서 캐치해서 반드시 예외처리를 할 수 있게 표시해줌. * 에러 발생시 roll-back 되지 않음.
- UncheckedException : 런타임 단계에서 확인되는 예외 상황. 문법상 문제는 없지만 프로그램이 돌아가는 도중 예기치 않은 상황이 생겨 발생하는 예외 의미.
* 에러 발생시 roll-back
10.4.3 예외 처리 방법
예외 발생 시 이를 처리하는 방법은 크게 3가지
- 예외 복구 : 예외 상황을 파악해서 문제를 해결하는 방식 try/catch 구문
- 예외 처리 회피 : 예외가 발생한 메서드를 호출한 곳에서 에러처리를 할 수 있게 전가하는 방식.
throw를 사용해 어떤 예외가 발생했ㄴ느지 호출부에 내용을 전달할 수 있음.
- 예외 전환 : 예외 발생 시 어떤 예외가 발생했는지에 따라 호출부로 예외 내용을 전달하면서 좀 더 적합한 예외 타입으로 전달할 필요가 있음. try/catch 방식을 활용하면서 catch 블록에서 throw 키워드를 통해 다른 예외 타입으로 전달하면 됨.
10.4.4 스프링 부트의 예외 처리 방식
예외 발생했을 때 클라이언트에 오류 메세지 전달하려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야함. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식 두 가지
1. @(Rest)ControllerAdvice와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리
2. @ExceptionHandler를 통한 특정 컨트롤러의 예외를 처리
@RestControllerAdvice 대신 @RestControllerAdvice를 사용하면 결과값을 JSON 형태로 반환할 수 있음.
- @RestControllerAdvice : 스프링에서 제공하는 어노테이션. 이 어노테이션은 @RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행.
별도 설정을 통해 예외 관제하는 범위 지정 가능
@RestControllerAdvice(basePackages = "com.springboot.valid_exception")
- @ExceptionHandler : @Controller나 @RestController가 적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용. 어떤 예외 클래스를 처리할 지는 value 속성으로 등록
@ExceptionHandler(value = RuntumeException.class)
value 속성은 배열 형식으로도 전달받을 수 있어서 여러 예외 클래스를 등록할 수도 있음.
구체적인 클래스가 지정된 쪽이 우선순위를 갖게 된다.
@ControllerAdvice의 글로벌 예외처리와 @Controller내의 컨트롤러 예외처리에 동일한 예외처리를 하면 범위가 좁은 컨트롤러의 핸들러 메서드가 우선순위를 갖게됨.
10.4.5 커스텀 예외