본문으로 건너뛰기

SpringBoot의 Validation을 사용 해 보자

Spring Validation이란?

What is Spring Validation?

validation이란 프로그래밍에 있어서 가장 필요한 부분이다.

Java/Kotlin 에서는 null값에 대해 접근하려고 할 때, null point exception이 발생한다. 이런 부분을 방지하기 위해 미리 검증하는 과정을 Validation이라고 함

Validation is one of the most essential parts of programming.

In Java/Kotlin, when you try to access a null value, a null pointer exception occurs. The process of verifying data in advance to prevent such issues is called Validation.

validation

Validation

  • 검증할 값이 많을경우 코드가 복잡
  • validation은 재사용성이 높아야하고, Service Logic과의 분리가 필요
  • Logic이 변경되어야 할 때에 Validation이 같이 들어가면 매우 난잡해진다
  • Code becomes complex when there are many values to validate
  • Validation should be highly reusable and needs to be separated from Service Logic
  • When Logic needs to be changed, having Validation mixed in makes things very messy

validaton을 할 수 있는 Annotation

Annotations for Validation

Size문자길이측정Future미래날짜
NotNullnull 불가FutureOrPresent오늘이거나 미래
NotEmptynull, '' 불가Pattern정규식
NotBlanknull,'', ' ' 불가Max최대값
Past과거 날짜Min최소값
PastOrPresent오늘이나 과거날짜Validobject validation

우선 validation을 적용 할 곳을 고민해보자. 나는 현재 시작하는 프로젝트에 간단하게 적용 해 보려고한다. 회원가입하는 곳에서 사용자 정보를 가지고왔을 때 체크 해주는 부분을 작성해보고자한다

First, let's think about where to apply validation. I'm going to apply it simply to a project I'm currently starting. I want to write a part that checks user information when it's received during registration.

데이터를 받아 올 DTO

DTO for Receiving Data

  • getter와 setter, toString은 너무 길어 생략했다
  • getter, setter, and toString are omitted as they are too long
package com.wool.modulink.dto.user;

public class User {

private String name;
private String password;
private String email;
private String phone;
private int age;
// getter, setter, toString
}

컨트롤러

Controller

package com.wool.modulink.controller.user;

import com.wool.modulink.dto.user.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

@PostMapping("/user")
public User user(@RequestBody User user) {
System.out.println(user);
return user;
}
}

localhost:8080/auth/user 로 POST 전송하기

Sending a POST Request to localhost:8080/auth/user

{
"name":"test",
"password":"1234",
"email":"paullee@mail.com",
"phone":"01012341234",
"age":1000
}
  • 위의 데이터는 언뜻봐서는 괜찮은 데이터같다.
  • email, phone 과 같은 경우는 쓰는 사람들마다 형식이 다를 수 도 있다
  • age같은 경우는.. 아직까지 1000년정도 살아있는 사람은... 보지못했기때문에...! 나이도 입력제한을 걸어주어야겠다
  • 포스팅의 목적에 맞게 Service Logic에서가 아닌, Spring Validation을 사용 해보자
  • At first glance, the data above looks fine.
  • For email and phone, the format may vary depending on the user.
  • As for age... I haven't seen anyone who has lived for about 1000 years...! We should also set input restrictions for age.
  • In line with the purpose of this post, let's use Spring Validation instead of Service Logic.

validation 적용하기

Applying Validation

기존의 방식

The Traditional Approach

기존에는 아래와같이 if문을 사용해서 컨트롤러 내부나 서비스로직 내부에서 검사를했다

Previously, validation was done inside the controller or service logic using if statements like below.

package com.wool.modulink.controller.user;

import com.wool.modulink.dto.user.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

@PostMapping("/user")
public ResponseEntity user(@RequestBody User user) {
System.out.println(user);

if(user.getAge() > 200){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(user);
}
if(user.getEmail().contains("@")){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(user);
}

return ResponseEntity.ok(user);
}
}

이제 위의 부분을 좀 더 Spring이 제공하는 방법들로 이쁘고 간편하게 바꿔보려고한다

기본적으로 제공되는 Validation들이 적용되기 위해서는 Valid 하고자하는 곳의 데이터에 어노테이션이 적용되어야한다.

기본적인 컨트롤러에 우선 Valid를 붙이고 다음으로 넘어가자

Now I'm going to change the above to something cleaner and simpler using methods provided by Spring.

For the built-in Validations to be applied, annotations must be applied to the data where you want to validate.

Let's first add Valid to the basic controller and move on.

Email Validation

Email Validation

이메일은 DTO에서 이메일을 담는 변수에 @Email 어노테이션을 사용 해 주면 된다.

그리고 해당하는 어노테이션이 동작하기 위해서는, 컨트롤러의 RequestBody 앞쪽에 @Valid 어노테이션으로 "검증을 할 것이다" 라고 스프링에게 알려주어야 한다

For email, you can use the @Email annotation on the variable that holds the email in the DTO.

And for the annotation to work, you need to tell Spring "I will validate" by adding the @Valid annotation in front of the RequestBody in the controller.

UserDto

import javax.validation.constraints.Email;

public class User {

private String name;
private String password;
@Email
private String email;
private String phone;
private int age;
}

UserController

package com.wool.modulink.controller.user;

import com.wool.modulink.dto.user.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/auth")
public class AuthController {

@PostMapping("/user")
public ResponseEntity user(@Valid @RequestBody User user) {
System.out.println(user);
}
}

Phone Validation

Phone Validation

이부분은 @Pattern 어노테이션의 regexp 옵션을 사용해서 정규식을 사용하는 모든 곳에 적용 할 수 있다

This part can be applied anywhere that uses regular expressions by using the regexp option of the @Pattern annotation.

UserDto

package com.wool.modulink.dto.user;

import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;

public class User {

private String name;

private String password;

@Email
private String email;

@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$",message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
private String phone;

private int age;
}

Max/Min 값 지정하기

Setting Max/Min Values

숫자가 들어오는 경우 나이는 1살 이상, 100살 이하로 제한을 둘 수 있다.

각각의 상황에 맞게 max/min 값을 지정 해 주어야 한다

When numbers are entered, age can be restricted to at least 1 year old and no more than 100 years old.

You should set max/min values according to each situation.

UserDto

public class User {

private String name;

private String password;

@Email
private String email;

@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$",message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
private String phone;

@Min(value = 0,message = "적절한 연령을 입력해주세요.")
@Max(value = 150,message = "적절한 연령을 입력해주세요.")
private int age;
}

validation error 모아받기

Collecting Validation Errors

validation을 한번에 모아받는 친구가 존재한다. BindingResult 라는 친구인데, validation에서 실패한 모든값을 들고있다. 반복문을 통해 error 메시지를 뽑아 줄 수 있다.

There is something that collects all validations at once. It's called BindingResult, and it holds all values that failed validation. You can extract error messages through a loop.

public ResponseEntity user(@Valid @RequestBody User user, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
StringBuilder sb = new StringBuilder();
bindingResult.getAllErrors().forEach(objectError -> {
FieldError field = (FieldError) objectError;
String message = objectError.getDefaultMessage();

System.out.println(field.getField() + ": " + message);

sb.append("field: " + field.getField());
sb.append("message: " + message);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
}
}

테스트 해보자

Let's Test It

{
"name":"test",
"password":"1234",
"email":"paulleeemail.com",
"phone":"01012341234",
"age":1000
}

위의 데이터를 전송했을 때, 콘솔창에 아래와 같이 나온다

When the above data is sent, the following appears in the console.

phone: 핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx
age: 적절한 연령을 입력해주세요.
email: 올바른 형식의 이메일 주소여야 합니다

API return값도 잘 꾸며주도록 나중에 작업 하면 좋을 것 같다.

It would be nice to work on formatting the API return values nicely later.