
그렇기에 Spring boot validation의 dependency를 통해, 손쉽게 검증을 진행할 수 있다!
implemetation("org.springframework.book:spring-boot-starter-validation")
http://beanvalidation.org/2.0-jsr380/
"^\\d{2,3}-\\d{3,4}-\\d{4}$"

@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {
@PostMapping("")
public UserRegisterRequest register(
@RequestBody UserRegisterRequest userRegisterRequest
){
log.info("init : {}", userRegisterRequest);
return userRegisterRequest;
}
}
{
"name" : "",
"password" : "",
"age" : 20,
"email" : "",
"phone_number" : "010-1111-2222",
"register_at" : ""
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {
@NotNull // != null
@NotEmpty // != null && name != ""
@NotBlank // != null && name != "" && name != " "
private String name;
@NotBlank
@Size(min = 1, max = 12)
private String password;
@NotNull
@Min(1)
@Max(120)
private Integer age;
@Email
private String email;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "휴대폰 번호 양식과 맞지 않습니다")
private String phoneNumber;
@FutureOrPresent // 현재 or 미래
private LocalDateTime registerAt;
}
@Pattern 어노테이션을 이용해 넣어주었다public UserRegisterRequest register(
@Valid
@RequestBody UserRegisterRequest userRegisterRequest
){
@Valid 어노테이션을 넣어줘, 요청받는 데이터를 검증한다!{
"name" : "홍길동",
"password" : "",
"age" : 20,
"email" : "hong@gmail.com",
"phone_number" : "010-1111-2222",
"register_at" : "2024-06-17T09:09:09"
}
default message [공백일 수 없습니다]]
[Field error in object 'userRegisterRequest' on field 'password': rejected value [];
{
"name" : "홍길동",
"password" : "qwer",
"age" : 20,
"email" : "hong@gmail.com",
"phone_number" : "010-1111-2222",
"register_at" : "2024-06-17T09:09:09"
}


@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Api<T> {
private String resultCode;
private String resultMessage;
@Valid
private T data;
private Error error;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
// inner class로 Error 만들기
public static class Error{
private List<String> errorMessage;
}
}
@Valid를 붙어줘야지 검증이 완료된다✅ 여기선 inner class를 사용하였다!
- 코드를 분리하는 것도 좋은 방법일 것 같다
- 여기서 Error 클래스는, Api에서만 사용할 것이므로 inner class를 사용했다
{
"result_code" : "",
"result_message" : "",
"data" : {
},
"error" : {
"error_message" : [
]
}
}
{
"result_code" : "",
"result_message" : "",
"data" : {
"name" : "홍길동",
"password" : "qwer",
"age" : 20,
"email" : "hong@gmail.com",
"phone_number" : "010-1111-2222",
"register_at" : "2024-06-17T09:09:09"
} ,
"error" : {
"error_message" : [
]
}
}

BindgingResult과 if문을 이용해 에러를 처리할 수 있다@Slf4j
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Api> validationException(
MethodArgumentNotValidException exception
){
log.error("", exception);
List<String> errorMessageList = exception.getFieldErrors().stream()
.map(it -> {
String format = "%s : { %s } 은 %s";
String message = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return message;
}).collect(Collectors.toList());
Api.Error error = Api.Error
.builder()
.errorMessage(errorMessageList)
.build();
Api<Object> errorResponse = Api.builder()
.resultCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.resultMessage(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(error)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
}
MethodArgumentNotValidException를 처리하는 ExceptionHandler을 하나 만들어줬다List<String> errorMessageList = exception.getFieldErrors().stream()
.map(it -> {
String format = "%s : { %s } 은 %s";
String message = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return message;
}).collect(Collectors.toList());
@RestControllerAdvice와 @ExceptionHandler로 에러들을 가져왔다Api.Error error = Api.Error
.builder()
.errorMessage(errorMessageList)
.build();
Api<Object> errorResponse = Api.builder()
.resultCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.resultMessage(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(error)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
{
"result_code" : "",
"result_message" : "",
"data" : {
"name" : "",
"password" : "qwer",
"age" : 20,
"email" : "hong@gmail.com",
"phone_number" : "010-11112222",
"register_at" : "2024-06-17T09:09:09"
} ,
"error" : {
"error_message" : [
]
}
}

이렇게, ✅ 서버의 스펙을 맞춘 후에 예외를 exception 핸들로로 빼게 되면
- 해당 비지니스 로직을 처리되는 곳에서는, 요청 안의 값이 유효하다는 장점이 생겨, 비지니스 로직만들 처리하는 기능을 가지게 된다!
- 또한 exception handler에서 format을 이용해 메시지를 만들어서 보내주므로 클라이언트가 오류를 쉽게 알 수 있는 장점이 있다
private String name;
private String nickName;
@AssertTrue(message = "name or nickName 중 반듯이 1개가 존재해야 합니다")
public boolean nameCheck(){
if (Objects.nonNull(name) && !name.isBlank()){
return true;
}
if (Objects.nonNull(nickName) && !nickName.isBlank()){
return true;
}
return false;
}
{
"result_code" : "",
"result_message" : "",
"data" : {
"name" : "",
"nick_name" : "",
"password" : "qwer",
"age" : 20,
"email" : "hong@gmail.com",
"phone_number" : "010-1111-2222",
"register_at" : "2024-06-17T09:09:09"
} ,
"error" : {
"error_message" : [
]
}
}
🤔 AssertTrue는 반듯이 is를 붙인 메서드 즉, boolean을 반환하는 메서드와 사용해야 한다고 한다!
@AssertTrue(message = "name or nickName 중 반듯이 1개가 존재해야 합니다")
public boolean isNameCheck(){
if (Objects.nonNull(name) && !name.isBlank()){
return true;
}
if (Objects.nonNull(nickName) && !nickName.isBlank()){
return true;
}
return false;
}
