3 분 소요


Spring Validation이란?

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

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

validation

  • 검증할 값이 많을경우 코드가 복잡
  • validation은 재사용성이 높아야하고, Service Logic과의 분리가 필요
  • Logic이 변경되어야 할 때에 Validation이 같이 들어가면 매우 난잡해진다

validaton을 할 수 있는 Annotation

Size 문자길이측정 Future 미래날짜
NotNull null 불가 FutureOrPresent 오늘이거나 미래
NotEmpty null, ‘’ 불가 Pattern 정규식
NotBlank null,’’, ‘ ‘ 불가 Max 최대값
Past 과거 날짜 Min 최소값
PastOrPresent 오늘이나 과거날짜 Valid object validation

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

데이터를 받아 올 DTO

  • getter와 setter, toString은 너무 길어 생략했다

    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
    }
    

컨트롤러

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 전송하기

{
    "name":"test",
    "password":"1234",
    "email":"paullee@mail.com",
    "phone":"01012341234",
    "age":1000
}
  • 위의 데이터는 언뜻봐서는 괜찮은 데이터같다.
  • email, phone 과 같은 경우는 쓰는 사람들마다 형식이 다를 수 도 있다
  • age같은 경우는.. 아직까지 1000년정도 살아있는 사람은… 보지못했기때문에…! 나이도 입력제한을 걸어주어야겠다
  • 포스팅의 목적에 맞게 Service Logic에서가 아닌, Spring Validation을 사용 해보자

validation 적용하기

기존의 방식

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

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를 붙이고 다음으로 넘어가자

Email Validation

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

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

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

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

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 값 지정하기

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

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

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 모아받기

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

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());
    }
}

테스트 해보자

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

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

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

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

댓글남기기