목차
10 유효성 검사와 예외 처리
ㄴ10.1 일반적인 애플리케이션 유효성 검사의 문제점
ㄴ10.2 Hibrenate Validator
ㄴ10.3 스프링 부트에서의 유효성 검사
ㄴ10.3.1 프로젝트 생성
ㄴ10.3.2 스프링 부트용 유효성 검사 관련 의존성 추가
ㄴ10.3.3 스프링 부트용 유효성 검사
ㄴ10.3.4 @Validated 활용
ㄴ10.3.5 커스텀 Validation 추가
ㄴ10.4 예외 처리
ㄴ10.4.1 예외와 에러
ㄴ10.4.2 예외 클래스
ㄴ10.4.3 예외 처리 방법
ㄴ10.4.4 스프링 부트의 예외 처리 방식
ㄴ10.4.5 커스텀 예외
ㄴ10.4.6 커스텀 예외 클래스 생성하기
10.1 일반적인 애플리케이션 유효성 검사의 문제점
일반적으로 사용되는 데이터 검증 로직의 문제점
- 계층별로 진행하는 유효성 검사는 검증로직이 각 클래스 별로 분산 되어 있어 관리하기가 어려움
- 로직에 의외로 중복이 많아 여러곳에 유사한 기능의 코드가 존재할 수 있음
- 검증해야할 값이 많다면 코드가 길어저 코드가 복잡해지고 가독성이 떨어짐
해결방안
- Bean Validation 어노테이션을 통해 다양한 데이터를 검증 -> 코드의 간결성 유지
Bean Validation을 사용하는 것은 유효성 검사를 위한 로직에 DTO 같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행한다는 의미이다.
10.2 Hibernate Validator
Hibernate Validator = Bean Validation 명세의 구현체
10.3 스프링 부트에서의 유효성 검사
해당 어노테이션을 사용하기 위해 dependency 추가
pom.xml
<dependencies>
<!-- 다른 의존성들 -->
<!-- Spring Boot 유효성 검사 관련 의존성 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
build.gradle
dependencies {
// 다른 의존성들
// Spring Boot 유효성 검사 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
스프링 부트에서 유효성 검사는 각 계층에서 전달을 주고받는 DTO 객체로 한다.
하는 방법
1. DTO 객체 클래스에서 각 유효성 검사를 어노테이션으로 한다.
예시 >
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class DtoObject {
@NoBlank
String name;
@Email
String email;
@Pattern(regexp = "01(?L0|1[6-9])[.-]?(\\d{3}||\\d{4})[.-]?(\\d{4})$")
String phoneNumber;
@Min(value = 20) @Max(value = 40)
int age;
@Size(min = 0, max = 40)
String description;
@Positive
int count;
@AssertTrue
boolean booleanCheck;
}
문자열 검증
- @Null : null 값만 허용
- @NotNull : null값을 허용하지 않음. "" or " "은 허용
- @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 : 이메일 형식을 검사, ""는 허용
자릿수 범위 검증
- BigDecimal, BigInteger, int, long 타입 지원
- @Digits(integer = $number1, fraction = $number2): $number1의 정수 자릿수와 $number2의 소수 자릿수를 허용
Boolean 검증
- @AssertTrue: true 값인지 체크, null값은 체크하지 않음
- @AssertFalse: false값인지 체크, null값은 체크하지 않음
문자열 길이 검증
- @Size(min = $min, max = $max): 최소 $min 길이에서 최대 $max 길이까지의 값 허용
정규식 검증
- @Pattern(regexp = "정규식 패턴"): 정규식 패턴에 맞는 값 허용
2. DTO 객체를 주고받는 컨트롤러에 @Valid 어노테이션을 포함시킨다.
<-- Controller의 일부 -->
@PostMapping("/valid")
public ResponseEntity<?> checkValidattionByValid(
@Valid @requestBody DtoObject dtoObject) {
}
또 다른 방법
1. @Valid가 아닌 @Validated 어노테이션 사용
Validated 어노테이션을 사용하면 @Validated(ValidationGroup1.class) 으로 해당 필드에서만 유효성을 검사할 수 도 있다.
2. 커스텀 Validation 추가
@커스텀화 가능 예를 들어 스프링이 제공하는 어노테이션 기능이 아닌 별도로 조합해서 커스텀 어노테이션을 사용해 유효성 검사를 해야할 때도 필요하다.
이 땐, ContraintValidator 인터페이스, 커스텀 어노테이션 인터페이스를 통해 가능하다.
만드는 방법
- ContraintValidator 인터페이스
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return value.matches("01(?L0|1[6-9])[.-]?(\\d{3}||\\d{4})[.-]?(\\d{4})$");
}
}
- 커스텀 어노테이션 인터페이스
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = TelephoneValidator.class)
public @interface Telephone {
String message() default "전화번호 형식이 일치하지 않습니다.";
Class[] groups() default {};
Class[] payload() default {};
}
Telephone 어노테이션 사용가능
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class DtoObject {
@NoBlank
String name;
@Email
String email;
@Telephone
String phoneNumber;
@Min(value = 20) @Max(value = 40)
int age;
@Size(min = 0, max = 40)
String description;
@Positive
int count;
@AssertTrue
boolean booleanCheck;
}
10.4 예외 처리
애플리케이션을 개발할 때 불가피하게 많은 오류들이 생기며 이를 try/catch, throw 구문을 활용해 처리해야한다.
자바의 예외 클래스는 다음과 같은 상속 구조를 갖추고 있으며, 보통
@ControllerAdvice와 @ExceptionHandler로 예외를 스프링 부트에서 파악할 수 있다.
나는 이것을 이용해서 Custom 예외처리하는 Custom exception을 만들어 두고, @ExceptionHandler로 해당 클래스를 참조해서 예외처리를 하는 편이다.
@Slf4j
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(AuthenticationException.class)
protected ResponseEntity<?> handleUserNotFoundException(AuthenticationException e) {
ErrorResponse errorResponse = ErrorResponse.builder()
.code(500)
.message(e.getMessage() + " 사용자가 존재하지 않습니다. 올바른 로그인 후 접근 권한이 필요합니다.")
.build();
return new ResponseEntity<>(errorResponse, HttpStatus.resolve(500));
}
@ExceptionHandler(AbstractException.class)
protected ResponseEntity<?> handleCustomException(AbstractException e) {
ErrorResponse errorResponse = ErrorResponse.builder()
.code(e.getStatusCode())
.message(e.getMessage())
.build();
return new ResponseEntity<>(errorResponse, HttpStatus.resolve(e.getStatusCode()));
}
@ExceptionHandler(RedisConnectionException.class)
protected ResponseEntity<?> handleRedisConnectionException(RedisConnectionException e) {
ErrorResponse errorResponse = ErrorResponse.builder()
.code(500)
.message(e.getMessage() + " Redis 서버가 연결되지 않았습니다.")
.build();
return new ResponseEntity<>(errorResponse, HttpStatus.resolve(500));
}
'BookStudy > 스프링 부트 핵심 가이드' 카테고리의 다른 글
[스프링 부트 핵심 가이드] 12 서버 간 통신 (1) | 2023.10.08 |
---|---|
[스프링 부트 핵심 가이드] 11 액추에이터 활용하기 (1) | 2023.10.08 |
[스프링 부트 핵심 가이드] 09 연관관계 매핑 (0) | 2023.09.23 |
[스프링 부트 핵심 가이드] 08. Spring Data JPA 활용 (0) | 2023.09.17 |
[스프링 부트 핵심가이드] 06. 데이터베이스 연동 (0) | 2023.09.08 |