본문 바로가기
실습/리눅스 서버 + 스프링 부트

Spring Validation

by 이민우 2024. 5. 7.
728x90
반응형

프로젝트가 끝나고 종료 보고까지 잠시 할 게 없어서 인터넷을 뒤적거리며 이런 저런 개발 자료를 보던 중 처음 보는 것을 발견했다.

 

지금까지 Controller에서 사용자 입력을 받으면 코드 내에서 해당 입력에 대한 검증을 수행했었다. 이것이 당연하다고 생각하고 있었기 때문이다.

 

하지만 간단한 검증 작업은 굳이 수기로 하지 않아도 됐었다. 그냥 Spring Validation을 사용하면 알아서 사전에 설정한 검증을 수행해주는 것이었다..!!!

처음 알았다..!!!

 

사실 입력값 검증이라는 것이 간단한 것만 하는 것이 아니기에, 이러한 라이브러리를 알게 되었어도 이를 자주 사용할지는 의문이다.

 

하지만 이는 단지 내 사고가 편협한 것일 수도 있고, 또한 어떤 지식이던 알아두면 좋다는 신념 하에 한 번 공부를 해볼까 한다.

 

참고로 위에서 이런 저런 개발 자료를 보던 중 발견한 웹 사이트는 아래의 웹 사이트였다.

https://jeong-pro.tistory.com/203

 

스프링 부트에서 Request 유효성 검사하는 방법, 서버 개발한다면 꼭 해야하는 작업 Spring Validation

스프링부트에서 Request로 오는 객체(DTO)를 어떻게 검증하는가에 대한 이야기 데이터 검증(validation)은 여러 계층에 걸쳐서 발생하는 흔한 작업이다. 어떻게하면 깔끔하게 유효성 검사를 할 수 있

jeong-pro.tistory.com

 

 

spring-boot-starter-validation

Spring Boot 어플리케이션에서 데이터 유효성 검사를 쉽게 수행할 수 있도록 지원하는 스타터의 일종이다.

 

해당 스타터를 사용하면 사용자가 Restful API에서 입력한 요청 바디의 유효성을 검사하는 로직을 간단하게 구현할 수 있다.

 

그 외에 크게 설명할 부분은 없으니, 바로 실습으로 넘어가서 어떻게 사용하는지 확인해보자.

 

프로젝트 생성

실습을 위해 아래와 같이 프로젝트를 생성한다.

 

클래스는 간단하게 Controller와, 각종 데이터 타입에 대한 Dto만을 다루어본다.

 

Validation은 당연히 DTO에 들어가서 각 멤버 변수에 어노테이션이 붙어 사용된다.

 

DTO는 간단하게 String, Numeric, Date, Object에 대한 Validation 사용만을 확인해본다.

 

각 DTO의 코드는 아래와 같으며, 각 어노테이션의 활용도 기재해놓았다.

 

StringDto.java

package com.mwlee.springvalid.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class StringDto {
	
	// null 혹은 "" 불가능 (""은 trim했을 때 기준)
	@NotBlank(message = "a Column Can't Be Null Or \"\"")
	private String a;
	
	// null 불가능
	// 비단 String뿐 아니라 어디서도 사용됨
	@NotNull(message = "b Column Can't Be Null")
	private String b;
	
	// Size 체크
	// max, min 혼용 혹은 별도 사용 가능. 일단 max만 사용.
	@Size(max=10, message = "c Column's Length Must Same Or Lower Than 10")
	private String c;
	
	// Email 형식인지 확인
	@Email(message = "d Column Is Not EMail")
	private String d;
	
	// 혼용해서 사용 가능
	@NotNull(message = "e Column Can't Be Null")
	@Size(max = 20, message = "e Column's Length Must Same Or Lower Than 20")
	@Email(message = "e Column Is Not EMail")
	private String e;
	
}
  • NotBlank : NULL 혹은 ""이 불가능하다. 이 때 ""은 trim()의 결과값이다.
  • NotNull : NULL이 불가능하다.
  • Size : String의 Length를 지정한다. max와 min을 지정할 수 있다.
  • Email : 이메일 형식의 String인지 확인한다.

 

NumericDto.java

package com.mwlee.springvalid.dto;

import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Negative;
import jakarta.validation.constraints.NegativeOrZero;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;


@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class NumericDto {

	// max 지정
	@Max(value = 100, message = "a Column Must Equals Or Lower Than 100")
	private int a;

	// min 지정
	@Min(value = -100, message = "b Column Must Equals Or Higher Than -100")
	private int b;

	// max, min 혼용 가능
	@Max(value = 100, message = "c Column Must Equals Or Lower Than 100")
	@Min(value = -100, message = "c Column Must Equals Or Higher Than -100")
	private int c;

	// 소수의 경우 이렇게 사용하면 됨
	@DecimalMax(value = "99.99", message = "d Column Must Equals Or Lower Than 99.99")
	@DecimalMin(value = "-99.99", message = "d Column Must Equals Or Higher Than -99.99")
	private double d;

	// Double에 DecimalMin, DecimalMax만 사용하라는 법은 없음.
	@Max(value = 100, message = "e Column Must Equals Or Lower Than 100")
	@Min(value = -100, message = "e Column Must Equals Or Higher Than -100")
	private double e;

	// NotNull과 혼용 가능
	@NotNull(message = "f Column Can't Be Null")
	@Max(value = 100, message = "f Column Must Equals Or Lower Than 100")
	@Min(value = -100, message = "f Column Must Equals Or Higher Than -100")
	private Integer f;

	// 음수만 받기
	@Negative(message = "g Column Can't Be Positive Or 0")
	private int g;

	// 양수만 받기
	@Positive(message = "h Column Can't Be Negative Or 0")
	private int h;

	// 0 혹은 음수만 받기
	@NegativeOrZero(message = "i Column Can't Be Positive")
	private int i;

	// 0 혹은 양수만 받기
	@PositiveOrZero(message = "j Column Can't Be Negative")
	private int j;

}
  • Max : Max를 지정한다.
  • Min : Min을 지정한다.
  • DecimalMax : 소수점이 포함된 Max를 지정한다. 이 때 value는 String으로 입력해야 한다.
  • DecimalMin : 소수점이 포함된 Min을 지정한다. 이 때 value는 String으로 입력해야 한다.
  • Negative : 음수만 받는다.
  • NegativeOrZero : 음수 혹은 0만 받는다.
  • Positive : 양수만 받는다.
  • PositiveOrZero : 양수 혹은 0만 받는다.

 

DateDto.java

package com.mwlee.springvalid.dto;

import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.PastOrPresent;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import java.util.Date;

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class DateDto {
	
	// 현재보다 미래 일자만 받기
	@Future(message = "A Column Must Be Later Than Now")
	private Date A;
	
	// 현재 혹은 미래 일자만 받기
	@FutureOrPresent(message = "B Column Must Be Now Or Later Than Now")
	private Date B;
	
	// 현재보다 과거 일자만 받기
	@Past(message = "C Column Must Be Before Than Now")
	private Date C;
	
	// 현재 혹은 과거 일자만 받기
	@PastOrPresent(message = "D Column Must Be Now Or Before Than Now")
	private Date D;
	
}
  • Future : 현재보다 미래의 일자만 받는다.
  • FutureOrPresent : 현재 혹은 미래 일자만 받는다.
  • Past : 현재보다 과거의 일자만 받는다.
  • PastOrPresent : 현재 혹은 과거 일자만 받는다.

 

마지막으로 Object에 대해서는 List와 Map 정도에 사용이 가능하다. 예시는 List만 보여주고 있으나, 전부 Map에도 적용 가능하다는 이야기이다.

 

ObjectDto.java

package com.mwlee.springvalid.dto;

import java.util.List;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class ObjectDto {
	
	/**
	 * 예시를 List로만 해놨는데, Map도 가능.
	 */

	// null 불가능
	@NotNull(message = "a Column Can't Be Null")
	private List<String> a;
	
	// null 혹은 size=0 불가능
	@NotEmpty(message="b Column Can't Be Null Or Empty")
	private List<String> b;
	
	// size 체크
	@Size(min=1, message = "c Column's Length Must Same Or More Than 1")
	private List<String> c;
	
}

 

내용 자체는 StringDto에 기재한 것과 똑같으니 패스한다.

 

 

마지막으로 Controller를 작성한다. 이 때 주의해야할 점이 있는데, @RequestBody 뒤에 @Valid 어노테이션이 붙어야 한다는 점이다.

 

또한 Valid의 결과를 각 컨트롤러의 파라미터에 설정해주어야 한다. 양식은 BindResult이다.

 

 

이제 간단하게 작성된 Controller 코드를 작성해보자.

package com.mwlee.springvalid.controller;

import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.mwlee.springvalid.dto.*;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;

// 간단하게 받아서 출력만
@Slf4j
@RestController
public class DtoRestController {
	
	@PostMapping("/dto/date")
	private void getDateDto(@RequestBody(required=true) @Valid DateDto dto, BindingResult bindingResult) {
		
		if(bindingResult.hasErrors()) {
			// 에러 존재 식별
			for(ObjectError oe : bindingResult.getAllErrors()) {
				log.error("{} RequestBody Has Such Error : {}", oe.getObjectName(), oe.getDefaultMessage());
			}
		}
		
		log.info(dto.toString());
		
	}
	
	@PostMapping("/dto/string")
	private void getStringDto(@RequestBody(required=true) @Valid StringDto dto, BindingResult bindingResult) {
		
		if(bindingResult.hasErrors()) {
			// 에러 존재 식별
			for(ObjectError oe : bindingResult.getAllErrors()) {
				log.error("{} RequestBody Has Such Error : {}", oe.getObjectName(), oe.getDefaultMessage());
			}
		}
		
		log.info(dto.toString());
		
	}
	
	@PostMapping("/dto/numeric")
	private void getNumericDto(@RequestBody(required=true) @Valid NumericDto dto, BindingResult bindingResult) {
		
		if(bindingResult.hasErrors()) {
			// 에러 존재 식별
			for(ObjectError oe : bindingResult.getAllErrors()) {
				log.error("{} RequestBody Has Such Error : {}", oe.getObjectName(), oe.getDefaultMessage());
			}
		}
		
		log.info(dto.toString());
		
	}
	
	@PostMapping("/dto/object")
	private void getObjectDto(@RequestBody(required=true) @Valid ObjectDto dto, BindingResult bindingResult) {
		
		if(bindingResult.hasErrors()) {
			// 에러 존재 식별
			for(ObjectError oe : bindingResult.getAllErrors()) {
				log.error("{} RequestBody Has Such Error : {}", oe.getObjectName(), oe.getDefaultMessage());
			}
		}
		
		log.info(dto.toString());
		
	}
}

 

 

테스트

이제 테스트를 해보자. 하지 말라는 건 다 넣어서 테스트를 할 예정이다.

 

먼저 String에 대한 테스트이다.

Request
Result

 

모든 데이터가 예상대로 Valid에 실패했음을 확인했다.

 

이제 모든 데이터에 대한 Request / Result를 확인해보자.

 

Numeric Request
Numeric Result

 

Date Request
Date Result

 

Object Request
Object Result

728x90
반응형