spring boot Validation

이서영·2024년 5월 17일

SpringBoot

목록 보기
3/7

spring boot Validation

1) validation

  • 회원가입 요청을 받는다거나 정보등을 받을 때, 간혹 빈 문자열로 보내는 것이거나 매칭이 안되는 케이스로 보내는 경우가 있을 수 있다.
  • 서비스 로직을 처리하기 전에 검증하는 로직을 만들어 처리를 하게 될 경우 요청받는 데이터에 따라서 매우 길어질 수 있다.
  • 그렇기 떄문에 spring boot에서는 spring boot -starter - validation이라는 dependency를 통해서 validation을 할 수 있다.
  • bean validation을 통해서 spec확인 가능

1-1) 어노테이션

  • @size : 특정 문자길이 특정(문자열만)
  • @NotNull : null 불가
  • @NotEmpty : null,"" 불가
  • @NotBlank : null,"", ""불가
  • @Pattern : 정규식 사용
  • @Max/Min : 최대/최소
  • @AssertTrue /False : 별도 Logic적용
  • @Valid : 해당 object validation 실행
  • @Past : 과거날짜
  • @PastOrPresent : 오늘이거나 과거
  • @Future : 미래
  • @ FutureOrPresent : 오늘이거나 미래
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {
    @PostMapping("")
    public Api<UserRequestRegister> resgister(
            @Valid
            @RequestBody Api<UserRequestRegister> userRequestRegister ){

        log.info("da:{}",userRequestRegister);
        return userRequestRegister;
    }
}


@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRequestRegister {
    @NotBlank
    private String name;

    @NotBlank
    @Size(min =1 , max = 11)
    private String password;
    @Min(1)
    @Max(100)
    @NonNull
    private Integer age;
    private String email;

    private String phone;
    @FutureOrPresent
    private LocalDateTime registerat;

}
public class Api<T> {
    private String resultCode;
    private String resultMessage;
    private T data;
    private Error error;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Error{
        private List<String> errorMessage;
    }
  • Api라는 공통된 스펙을 만들어서 항상 Api를 통해서 json 객체를 송수신.
{
  "resultCode" : "",
  "resultMessage":"",
  "data" : 
  {
    "name":"홍길동",
    "password":"sdadsadsa21",
    "age":10,
    "email":"dsadasdas@gmail.com",
    "phone": "010-3014-0000",
    "registerat":"2025-05-17T12:03:03"
  },
  "error" : {
    "errormessage": [
    ]
  }
}

이런 방식으로 전송해야된다.

  • 또한 요청을 보낼때에도 검증을 보내기 때문에 data위에 @Valid 어노테이션을 붙여야된다.

  • 아래의 코드는 에러를 잡는 코드를 추가한 것이다.

package com.example.Validation.Controller;

import com.example.Validation.Model.Api;
import com.example.Validation.Model.UserRequestRegister;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/api/user")

public class UserApiController {

    //회원가입
    @PostMapping("")
    public Api<? extends Object> resgister(
            @Valid
            @RequestBody Api<UserRequestRegister> userRequestRegister,
            BindingResult bindingResult  //에러를 잡기위한 bindingresult

    ){
        /// 어떠한 에러가 잡히먄 그것을 stream으로 field로 가져와서 map을 통해 변경 후 collection의 리스트로 반환
        if(bindingResult.hasErrors()){
            var errorMessageList = bindingResult.getFieldErrors().stream()
                    .map(it -> {
                var format ="%s: {%s}는 %s";
                var message = String.format(it.getField(),it.getRejectedValue(),it.getDefaultMessage());
                return message;
                    }).collect(Collectors.toList());
            //api error에 들어갈 error 을 만든다
            var error = Api.Error.builder()
                    .errorMessage(errorMessageList).build();
            var errorResponse = Api.builder()
                    .resultCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
                    .resultMessage(HttpStatus.BAD_REQUEST.getReasonPhrase())
                    .error(error)
                    .build();
            return errorResponse;
        }
        //



        log.info("da:{}",userRequestRegister);
        var body = userRequestRegister.getData();
        Api<UserRequestRegister> response = Api.<UserRequestRegister>builder()
                .resultCode(String.valueOf(HttpStatus.OK.value()))
                .resultMessage(HttpStatus.OK.getReasonPhrase())
                .data(body)
                .build();

        return response;
    }
}
  • 여기에서 post를 하면 200 ok가 떨어진다. 그렇기 떄문에 이것을 해결하기위해 exceptionhandler로 넘기도록 하겠다.
  • 따로 패키지를 만들어두고 그것을 exceptionhandler를 만든후에
package com.example.Validation.Exception;

import com.example.Validation.Model.Api;
import jakarta.validation.constraints.Size;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class VaildationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Api> vaildexception(MethodArgumentNotValidException exception){
        log.info("",exception);
        var errorMessageList = exception.getFieldErrors().stream()
                .map(it -> {
                    var format ="%s: {%s}는 %s";
                    var message = String.format(it.getField(),it.getRejectedValue(),it.getDefaultMessage());
                    return message;
                }).collect(Collectors.toList());
        //api error에 들어갈 error 을 만든다
        var error = Api.Error.builder()
                .errorMessage(errorMessageList).build();

        var 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);
    }
    //

    }

이렇게 옮기면 된다

validation 커스텀(복합적인 조건)

  • 가정으로 닉네임과 이름 둘중 하나라도 있으면 적지 않아도 된다는 가정이 있을 떄 검증이 된다는 어노테이션은 없기에 사용자가 별도로 생성
  • function을 만들어서 가능
package com.example.Validation.Model;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRequestRegister {
    private String name;
    private String nickName;

    @NotBlank
    @Size(min =1 , max = 11)
    private String password;
    @Min(1)
    @Max(100)
    @NonNull
    private Integer age;
    private String email;

    private String phone;
    @FutureOrPresent
    private LocalDateTime registerat;
    @AssertTrue(message = "name or nickname은 존재 해야한다") // 해당 리턴값이 ture일때 실행 되도록
    public boolean isnamecheck(){  //무조건 앞에 is가 있어야한다.
        if(!name.isBlank()){return true;}
        if(!nickName.isBlank()){return true;}

        return false; // 실패하도록 지정
    }
}
  • 주의점은 무조건 boolean타입의 메소드는 is가 앞에 붙어있어야 동작

어노테이션 만들기

profile
전공자 학생

0개의 댓글