본문 바로가기

SpringBoot

[SpringBoot] 유효성 검사(Validation)

728x90

 

 

 

 

 


 

유효성 검사 ( Validation ) 

validation은 어떤 데이터의 값이 유효한지, 타당한지 확인하는 것을 의미한다

UI에서 JavaScript로 "이메일 양식이 일치하지 않는다"라고 보내는 것은 UX측면에서 사용자의 편의를 위함이다

보안적인 측면에서 UI, 서버 둘 다 유효성 검사가 수행되어야 한다

스프링, 스프링 부트 모두 적용 가능하다

 

 

 

스프링 부트 2.3버전 이후 Spring Boot Starter Validation 라이브러리 필요하다!

 

1️⃣ Maven Repository에 들어가서 "Spring validation"이라고 검색하면

       다음과 같은 "Spring Boot Starter Validation"을 클릭한다

 

2️⃣ version은 어느 것으로 해도 상관없다 ( why? 어차피 스프링 부트 버전에 맞출 것이기 때문에! )

 

 

3️⃣ sts 4 내부에 있는 build.gradle 파일에 위에서 복사해온 코드를 삽입한다

      ( 단, 스프링 부트 버전에 맞출 것이기 때문에 위에서 체크한 버전은 지우고 넣어준다 )

 

 

 

유효성 검사를 진행하기 위해서는 어노테이션이 필요한데 어떠한 어노테이션이 있을까?

유효성 검사 어노테이션

  ✅ VO 클래스의 멤버변수에 적용해서 사용

  ✅ javax.validation 패키지를 사용 

 

  어노테이션   설명   적용대상
  @NotNull ⭐   null을 제외   String, Long, Integer 등 전부 검사 가능
  @NotBlank ⭐   null, 공백을 허용하지 않음   String만 적용 가능
  @NotEmpty   null을 허용하지 않음   String, Map, Array에 검사 가능
  @Pattern ⭐   정규표현식에 맞는 문자열을 정의 가능   String만 적용가능
  @Email   email 형식이어야 함   공백을 통과

 

ValidVO 클래스

package com.simple.basic.command;

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

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ValidVO {
	
	/*
	 * @NotNull - null값만 허용하지 않음 (wrapper의 integer, long, string등)
	 * @NotBlank - null값과 공백허용하지 않음 (String에만 적용)
	 * @NotEmpty - null값을 허용하지 않음(Array, list 적용)
	 * @Pattern - 정규표현식에 맞는 문자열을 정의할 수 있음(String에만 적용)	
	 * @Email - 이메일형식 검즌(공백은 통과)
	 * @Min - 최솟값
	 * @Max - 최댓값
	 * 
	 */
	
	@NotBlank(message = "이름은 필수입니다")
	private String name;
	
	//숫자, 실수형의 원시타입은 기본값이 0 이라서 공백 맵핑이 불가능하기 때문에 래퍼타입으로 선언하는 편이 좋다
	@NotNull(message = "급여는 필수입니다")
	private Integer salary;
	
	@Pattern(regexp = "[0-9]{3}-[0-9]{4}-[0-9]{4}" , message = "전화번호 형식은 000-0000-0000입니다")
	private String phone;
	
	@NotBlank(message = "email형식이어야 합니다") //동시에 적용 쌉가능
	@Email//email형식이어야 함, 단 공백은 통과
	private String email;
}

 

👩‍💻 이 부분은 알고 넘어가는 게 좋으니 짚고 갑시다

  //숫자, 실수형의 원시타입은 기본값이 0 이라서 공백 맵핑이 불가능하기 때문에 래퍼타입으로 선언하는 편이 좋다
  @NotNull(message = "급여는 필수입니다")
  private Integer salary;

 

원래 salary 변수는 int로 선언했다가 어떠한 문제가 발생해서 Integer로 변수 타입을 바꾸게 되었다

Why 왜일까?

int(원시타입)는 null값을 가질 수 없고 기본값 자체가 0이기에 input태그에서 값 없이 데이터를 넘기고 

vo로 그 값을 받는 타입이 int인 경우에 데이터 없이 공백이 생기면 유효성 검사 이전에 자바 내부에서

형변환 에러가 발생한다

그러나 vo에서 값을 받는 타입을 int가 아닌 Integer로 해주면,

wrapper형인 Integer는 null값을 가질 수 있어서 에러를 해결할 수 있다

 

 

그렇다면 유효성 검사는 어디에 적용해야 할까? ( 어디서 일어날까? )

 그곳은 바로 Controller!

  ✅ 컨트롤러에서 데이터를 받을 때,  @Valid와 Errors객체를 사용해 유효성 검사를 진행

 

 

 

 

이 때 필요한 프로그램 코드는 다음의 표를 참고!!

  설명   표현식   클래스명
  hasErrors ( )   바인딩된 에러가 있다면 true   Errors
  getFieldErrors ( )   유효성 검사에 실패한 필드 목록 확인   Errors
  getField ( )   유효성 검사에 실패한 변수명 확인   FieldErrors
  getDefaultMessage ( )   유효성 검사에 실패한 변수의 에러메시지 확인   FiledErrors
  isBindingFailure ( )   유효성 감사에 바인딩이 안 된 경우 false   FiledErrors

 

ValidController

//@Valid - 유효성검사를 진행, Errors - 유효성검사에 실패하면 에러객체로 바인딩
@PostMapping("/actionForm")
public String actionForm(@Valid ValidVO vo, Errors error, Model model) {
	
	if(error.hasErrors()) { //에러가 있다면 true, 에러가 없다면 false
		List<FieldError> list= error.getFieldErrors(); //에러가 발생된 목록

		for(FieldError err : list) {
		
			if(err.isBindingFailure()) {	//유효성 검사의 실패가 아니라, 자바 내부의 에러라면 true
			//System.out.println("자바 내부 에러 발생");
				model.addAttribute("valid_"+ err.getField(), "형식이 올바르지 않습니다");
			}else { //유효성 검사에 실패한 목록
				model.addAttribute("valid_" + err.getField(), err.getDefaultMessage());
					
			}
			
		} //반복문 끝
		model.addAttribute("vo", vo); //사용자가 작성한 값을 화면으로 
		return "valid/ex01";
	}

 

 

위의 코드를 하나하나 살펴보면...

  if(error.hasErrors()) { //에러가 있다면 true, 에러가 없다면 false

 

 위의 코드는 ValidVO 데이터를 유효성 검사를 진행했는데, 유효성 검사에 실패하여 Errors로 바인딩 된 후로, 

 error.hasErrors( )는 바인딩된 에러가 있는지 없는지를 확인하기 위한 코드이다

 

 

  List<FieldError> list= error.getFieldErrors(); //에러가 발생된 목록

 

 바인딩된 에러의 유무에 따라 에러가 있다면, 왜 유효성 검사에 실패했는지 그 목록을 알기 위해서 위의   getFieldErrors( )를 사용해 list에 담아주면 그 목록을 확인할 수 있다

 

 

for(FieldError err : list) {
			
	if(err.isBindingFailure()) {	//유효성 검사의 실패가 아니라, 자바 내부의 에러라면 true
	//System.out.println("자바 내부 에러 발생");
		model.addAttribute("valid_"+ err.getField(), "형식이 올바르지 않습니다");
	}else { //유효성 검사에 실패한 목록
		model.addAttribute("valid_" + err.getField(), err.getDefaultMessage());
					
	}
			
} //반복문 끝

 

위에서 유효성 검사에 실패한 필드 목록을 담아준 list를 FieldError 타입인 err에 담아 주고 반복문을 돌리는데

조건문을 통해 에러의 목록이 어떤 에러인지 확인할 수 있도록 처리해준다

  err.isBindingFailure ( ) 조건문을 통해

   ✅ 유효성 검사의 실패가 아니라 자바 내부의 에러인 경우

   ✅ 유효성 검사에 실패한 경우

  위 두 가지의 에러 경우를 구별할 수 있다

 

⭐ 어떤 에러인지 구분을 했으면 그에 맞는 에러 메시지를 화면에 뿌려주기 위해서 Model 객체를 이용해

       메세지를 화면에 출력되게 할 수 있다 

       ✅ getField( ) : 유효성 검사에 실패한 변수명

       ✅ getDefaultMessage( ) : 유효성 검사에 실패한 변수의 에러메시지

 

 

  model.addAttribute("vo", vo); //사용자가 작성한 값을 화면으로

 

이제 처리해줬던 모든 값을 화면에 출력이 될 수 있도록 모델 객체에 담아주면 컨트롤러에서 할 일은 끝!

  

 

 

그렇다면 이 값들이 화면에 어떻게 출력되는지, 어떤 흐름으로 실행되는지 확인해보자!

 

VO 객체(데이터)에서 유효성 검사를 위한 어노테이션을 주입해 놓고 이를 수행하는 건 컨트롤러에서 하게 된다

그런 다음 컨트롤러에서 올바르게 유효성 검사를 수행하고 나면 그 결과를 화면에 출력할 수 있도록 한다

 

 

 

input태그의 value값을 처리해주는 또다른 이유는 사용자의 편의를 위함이다!

대표적으로 회원가입 창에서 input태그의 value값의 위대함을 뼈저리게 느끼게 된다...ㅎ 

당신은 지금 어느 홈페이지에 가입하기 위해 10분가량을 회원가입 폼을 작성했고

이제 회원가입 버튼 하나만 누르면 완료가 되는 상황에 있다

그런데 필수요건이었던 닉네임을 작성하지 않았고 이로 인해 10분간 적은 모든 내용이 다 사라져서 다시 작성해야

한다고 생각해봐라..value값을 따로 지정해주지 않은 개발자들을 욕하게 될 것이다^^

따라서 다음 사진과 같이 어떠한 요건을 놓쳤더라도 다른 것들은 원래 작성한 대로 남아있도록 처리해주는 것이

정말 정말 중요하니까 기억해두자!