
: 스프링 부트를 활용한 애플리케이션 개발 실무
📝 목차
10장. 유효성 검사와 예외 처리
10-1. 일반적인 애플리케이션 유효성 검사의 문제점
10-2. Hibernate Validator
10-3. 스프링 부트에서의 유효성 검사
- 프로젝트 생성
- 스프링 부트용 유효성 검사 관련 의존성 추가
- 스프링 부트의 유효성 검사
- @Validated 활용
- 커스텀 Validation 추가
10-4. 예외 처리
- 예외와 에러
- 예외 클래스
- 예외 처리 방법
- 스프링 부트의 예외 처리 방식
- 커스텀 예외
- 커스텀 예외 클래스 생성하기
데이터를 사전 검증하는 작업 = 유효성 검사 또는 데이터 검증
➡️ 문제 해결을 위해 java진영에서는 Bean Validation이라는 유효성 검사 프레임워크 제공
Bean Validation을 사용한다는 것은 유효성 검사를 위한 로직을 DTO 같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행
어노테이션을 사용한 검증 방식이기 때문에 코드의 간결함 유지도 가능
: Bean Validation 명세의 구현체
스프링 부트에서는 Hibernate Validation를 유효성 검사 표준으로 채택
도메인 모텔에서 어노테이션을 통한 필드값 검증을 가능하게 도와준다.
spring-boot-starter-validation 의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
: 유효성 검사는 각 계층으로 데이터가 넘어오는 시점에 데이터에 대한 검사를 실시
스프링 부트 프로젝트에서는 계층 간 데이터 전송에 대체로 DTO객체를 활용하고 있기 때문에 유효성 검사를 DTO객체를 대상으로 수행하는 것이 일반적이다.
각 필드에 어노테이션 선언 ➡️ 각 어노테이션은 유효성 검사를 위한 조건을 설정
✅ 문자열 검증
✅ 최댓값/최솟값 검증
✅ 값의 범위 검증
✅ 시간에 대한 검증
✅ 이메일 검증
✅ 자릿수 범위 검증
✅ Boolean 검증
✅ 문자열 길이 검증
✅ 정규식 검증
@Valid 자바에서 지원하는 어노테이션
@Validated 스프링에서 사용 가능 -> @Valid 어노테이션 기능을 포함
@Validated는 유효성 검사를 그룹으로 묶어 대상을 특정할 수 있는 기능이 있다.
@Validated 어노테이션에 특정 그룹을 설정하지 않은 경우@Validated 어노테이션에 특정 그룹을 설정하는 경우: 유효성 검사 어노테이션에서 제공하지 않은 기능을 사용해야 할 때,
ConstraintValidator와 커스텀 어코테이션을 조합해서 별도의 유효성 검사 어노테이션 생성 가능
동일한 정규식을 계속 쓰는 @Pattern 어노테이션의 경우가 가장 흔한 사례
ConstraintValidator 인터페이스를 구현하는 클래스를 생성
(---Validator : 클래스명)
implement ConstraintValidator<> {
}
➡️ Validator 클래스를 ConstraintValidator 인터페이스의 구현체로 정의
인터페이스를 선언할 때, 어떤 어노테이션의 인터페이스인지 타입을 지정
: 자바에서는 try/catch, throw 구문을 활용해 처리한다.
스프링 부트에서도 예외 처리할 수 있는 기능을 제공한다!
예외(Exception)
: 애플리케이션이 정상적으로 동작하지 못하는 상황
에러 (Error)
: 주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없다.
발생 시점에 처리하는 것이 아니라 문제가 발생하지 않도록 예방해서 원천적으로 차단
➡️ 메모리 부족(OutOfMemory), 스택 오버플로(StackOverFlow) 등
모든 예외 클래스는 Throwalbe 클래스를 상속받는다.
Exception 클래스는 다양한 자식 클래스를 가지고 있는데,
Checked Exception과 Unchecked Exception으로 구분할 수 있다.
| Checked Exception | Unchecked Exception | |
|---|---|---|
| 처리 여부 | 반드시 예외 처리 필요 | 명시적 처리를 강제하지 않음 |
| 확인 시점 | 컴파일 단계 | 실행 중 단계 (런타임 단계) |
| 대표적인 예외 클래스 | IOException SQLException | RuntimeException NullPointerException IllegalArgumentException IndexOutOfBoundException SystemException |
✅ Checked Exception
: 컴파일 단계에서 확인 가능한 예외 ➡️ 예외 처리 반드시 필요
✅ Unchecked Exception
: 문법상 문제 ❌, 프로그램이 동작하는 도중 예기치 않은 상황으로 발생하는 예외
예외 복구
: 예외 상황을 파악해서 문제를 해결하는 방법
➡️ try/catch 구문
예외 처리 회피
: 예외가 발생한 시점에서 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 예외처리를 할 수 있게 전가하는 방식
➡️ throw 키워드 사용으로 어떤 예외가 발생했는지 호출부에 내용을 전달
예외 전환
: 예외 복구 + 예외 처리 회피 적절하게 섞은 방식
예외가 발생 ➡️ 호출부로 예외 내용 전달 && 적합한 예외 타입으로 전달
: 예외를 복구해서 정상으로 처리하기 보다는
요청을 보낸 클라이언트에 어떤 문제가 발생했는지 상황을 전달하는 경우가 많다.
Why? 외부에서 들어오는 요청에 담긴 데이터를 처리하는 경우가 많다.
✅ 클라이언트에 오류 메세지를 전달하려면,
각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 한다.
전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 두 가지가 있다.
@(Rest)ControllerAdvice와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리
(@ControllerAdvice대신 @RestControllerAdvice를 사용하면 결과값을 JSON 형태로 반환할 수 있다.)
ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리
커스텀 예외는 만드는 목적에 따라 생성하는 방법이 다르다!