애플리케이션의 비즈니스 로직이 올바르게 동작하도록 사전 검증하는 작업
데이터 유효성 검사 프레임워크. 어노테이션을 통해 다양한 데이터 검증 기능 제공.
유효성 검사를 위한 로직을 DTO 같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행.
Bean Validation 명세의 구현체. 스프링 부트 유효성 검사 표준
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
각 계층으로 데이터가 넘어오는 시점에 해당 데이터에 대한 검사 실시.
DTO 객체를 대상으로 수행하는 것이 일반적
requestDTO
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ValidRequestDto {
@NotBlank
String name; // null, "", " " 허용 X
@Email
String email;
@Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
String phoneNumber;
@Min(value = 20) @Max(value = 40)
int age; // 20살 이상 40살 이하
@Size(min = 0, max = 40)
String description;
@Positive
int count; // 0 아닌 양수
@AssertTrue
boolean booleanCheck; // true인지 체크
}
Controller
@RestController
@RequestMapping("/validation")
@Slf4j
public class ValidationController {
@PostMapping("/valid")
public ResponseEntity<String> checkValidationByValid(@Valid @RequestBody ValidRequestDto validRequestDto) {
log.info(validRequestDto.toString());
return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
}
}
유효성 검사 통과
⚠️ 앞서 설정한 규칙을 벗어나는 값은 400 에러 발생
public interface ValidationGroup1 {
}
public interface ValidationGroup2 {
}
두 인터페이스 모두 내부 코드 없음, 인터페이스만 생성해서 그룹화하는 용도로 사용
requestDTO
DTO 객체에 그룹 설정
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ValidatedRequestDto {
@NotBlank
private String name;
@Email
String email;
@Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
String phoneNumber;
@Min(value = 20, groups = ValidationGroup1.class)
@Max(value = 40, groups = ValidationGroup1.class)
private int age;
@Size(min = 0, max = 40)
private String description;
@Positive(groups = ValidationGroup2.class)
private int count;
@AssertTrue
private boolean booleanCheck;
}
Controller
@Validated 어노테이션 속성으로 그룹 지정
@RestController
@RequestMapping("/validation")
@Slf4j
public class ValidationController {
@PostMapping("/valid")
public ResponseEntity<String> checkValidationByValid(@Valid @RequestBody ValidRequestDto validRequestDto) {
log.info(validRequestDto.toString());
return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
}
@PostMapping("/validated/group1")
public ResponseEntity<String> checkValidation1(@Validated(ValidationGroup1.class) @RequestBody ValidatedRequestDto validatedRequestDto) {
log.info(validatedRequestDto.toString());
return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
}
@PostMapping("validated/group2")
public ResponseEntity<String> checkValidation2(@Validated(ValidationGroup2.class) @RequestBody ValidatedRequestDto validatedRequestDto) {
log.info(validatedRequestDto.toString());
return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
}
@PostMapping("validated/all-group")
public ResponseEntity<String> checkValidation3(@Validated({ValidationGroup1.class, ValidationGroup2.class}) @RequestBody ValidatedRequestDto validatedRequestDto) {
log.info(validatedRequestDto.toString());
return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
}
}
자바에서는 try/catch, throw 구문 활용
ㅇ 예외(exception)란?
입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황. 코드 설계를 통해 개발자가 직접 처리 가능
ㅇ 에러(error)란?
주로 자바의 가상머신에서 발생시키는 것으로 애플리케이션 코드에서 처리할 수 있는 것이 거의 없음.
ex) 메모리 부족(OutOfMemory), 스택 오버플로 등
모든 예외 클래스는 Throwable 클래스 상속받음
Exception 클래스는 크게 Checked Exception, Unchecked Exception 구분
try 블록 - 예외 발생 가능 코드 작성
catch 블록 - try 블록에서 발생하는 예외 상황 처리 내용 작성. 여러개 작성 가능(여러개의 catch 블록을 순차적으로 거치면서 예외 유형과 매칭되는 블록을 찾아 예외 처리 동작 수행)
int a = 1;
String b = "a";
try {
System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e) {
b = "2";
System.out.println(a + Integer.parseInt(b));
}
예외 발생 시점에서 바로 처리하는 것이 아니라 예외 발생 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식
throw 키워드 사용 - 어떤 예외가 발생했는지 호출부에 내용 전달
int a = 1;
String b = "a";
try {
System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e) {
throw new NumberFormatException("숫자가 아닙니다.");
}
Handler
@RestControllerAdvice
@Slf4j
public class CustomExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handleException(RuntimeException e, HttpServletRequest request) {
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
log.error("Advice 내 handleException 호출, {}, {}", request.getRequestURI(), e.getMessage());
Map<String, String> map = new HashMap<>();
map.put("error type", httpStatus.getReasonPhrase());
map.put("code", "400");
map.put("message", e.getMessage());
return new ResponseEntity<>(map, responseHeaders, httpStatus);
}
}
예외를 발생시킬 Controller
@RestController
@RequestMapping("/exception")
public class ExceptionController {
@GetMapping
public void getRuntimeException() {
throw new RuntimeException("getRuntimeException 메서드 호출");
}
}
컨트롤러로 요청이 들어오면 RuntimeException 발생
출처 : (책) 스프링 부트 핵심 가이드 / 장정우, 위키북스