본문 바로가기
Back-end/Spring

[Spring] chapter08 유효성 검사 기능 알아보기

by na1-4an 2023. 8. 1.

아래 글은 스프링 프레임 워크 첫걸음 책을 기반하여 작성한 글입니다.

 

뷰에 정보를 입력할 때 숫자를 입력해야한는 곳에 문자를 입력하게 되면 어떻게 될까?

이번 8장에서는 뷰에 입력한 값에 대해 입력 체크를 수행하는 유효성 검사기능에 대해 알아보겠다.

8-1 유효성 검사의 종류

(1) 유효성 검사란?

: 입력 내용이 요건에 만족하는지 그 타당성을확인하는 입력 체크!!로 크게 두 개로 나뉜다.

  1. 단일 항목 검사
  2. 상관 항목 검사(서로 관련이 있는 항목을 함께 체크하는 방법)

 

(2) 단일 항목 검사란?

: 입력 항목 대해 설정하는 입력 체크 기능이다.

Form 클래스 등의 필드에 어노테이션을 부여해서 사용한다.

주로 사용하는 어노테이션은 다음과 같다.

@NotNull @NotEmpty @NotBlank: 공백 검증. 아래 표 참고.

@Max @Min: 지정한 숫자 이하, 이상인 것을 검증한다.

@Size: 문자열이나 collection이 지정한 범위 내에 있는 것을 검증한다.

@AssetTrue @AssetFalse: 값이 true, false인 것을 검증한다.

@Pattern: 지정한 정규 표현과 일치하는 것을 검증한다.

@Range: 지정한 숫자 범위 안에 있는 것을 검증한다.

@DeciamlMax @DecimalMin: 지정한 숫자 이하, 이상인 것을 검증한다.

      → @Max, @Min과 달리 소숫점 이하까지 검증한다!

@Digits: 정수부와 소수부의 자릿수를 검증한다.

@Future @Past: 미래, 과거의 날짜인 것을 검증한다.

@Valid:중첩된 Form을 검증한다.

@Length: 문자열  범위가 지정한 범위 안에 있는 것을 검증한다.

       ex. @Length(min=0, max=10)

@Email @CreditCardNumber @URL: 문자열이 각 형식인지 검증한다.

어노테이션 null인 경우 공백문자("")인 경우 스페이스나 탭인 경우
@NotNull  체크 에러 허가 허가
@NotEmpty  체크 에러  체크 에러  허가
@NotBlank 체크 에러  체크 에러  체크 에러 

 

(3) 커스텀 유효성 검사란?

단일 항목 검사는 하나의 필드를 체크하지만, 상관 항목 검사는 여러 필드에 대해 혼합해서 체크한다.

상관 항목 검사 역시 두 가지 수행 방법이 있다.

  1. Bean Validation을 사용하는 방법.
  2. 스프링 프레임워크에서 제공하는 Validator 인터페이스를 구현하는 방법.

이 글에서는 2번 방법에 대해 다루도록 하겠다.


8-2 단일 항목 검사를 사용하는 프로그램 만들기

01 프로젝트 생성

 

02 Form 클래스 생성

com.example.demo.form 패키지를 만들어 CalcForm 자바 클래스를 생성한다.

package com.example.demo.form;

import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Range;

@Data
public class CalcForm {

    @NotNull(message ="왼쪽: 숫자를 입력해주세요")
    @Range(min=1, max=10, message ="왼쪽: {min}~{max} 범위의 숫자를 입력해주세요.")
    private Integer leftNum;

    @NotNull(message ="왼쪽: 숫자를 입력해주세요")
    @Range(min=1, max=10, message ="왼쪽: {min}~{max} 범위의 숫자를 입력해주세요.")
    private Integer rightNum;

}

 

03 컨트롤러 생성

controller 패키지를 만들어 ValidationController를 생성한다.

package com.example.demo.controller;

import com.example.demo.form.CalcForm;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;

@Controller
public class ValidationController {
    /*form-backing bean 초기화*/
    @ModelAttribute
    public CalcForm setupForm(){
        return new CalcForm();
    }
    
    /*입력 화면 표시*/
    @GetMapping("show")
    public String showView(){
        return"entry";
    }
}

유효성을 검사하기 위해서는 10~14번째 줄과 같이 'form-backing bean'의 설정이 필요하다.

HTML의 <form> 태그에 바인딩되는 Form 클래스 인스턴스를 'form-backing bean'이라 부르고 @ModelAttribute 어노테이션을 사용해서 연결한다.

 

'form-backing bean' 초기화는 @ModelAttribute 어노테이션을 부여한 메서드에서 작성한다.

 

04 뷰 생성(입력 화면)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>입력 화면</title>
</head>
<body>
    <form th:action="@{/calc}" method="post" th:object="${calcForm}">
        <div>
            <input type="text" th:field="*{leftNum}">
            +
            <input type="text" th:field="*{rightNum}">
        </div>
        <input type="submit" value="계산">
    </form>
</body>
</html>
  • th:object -  form 태그에 작성하여 해당 태그가 Model 객체에서 넘어온 객체의 필드값들을 참조하도록 만든다.
  • th:field - HTML 태그의 id, name, value 속성을 자동 처리해준다.
  • th:action - form 태그 사용 시, 해당 경로로 요청을 보낼 때 사용. (url)

 

05 컨트롤러에 추가

 /*확인 화면을 표시*/
    @PostMapping("calc")
    public String confirmView(@Validated CalcForm form, BindingResult bindingResult, Model model){
        //입력 체크에서 에러가 발생한 경우
        if (bindingResult.hasErrors()) {
            //입력 화면으로
            return "entry";
        }
        
        //값 더하기
        Integer result = form.getLeftNum() + form.getRightNum();
        
        //Model에 저장
        model.addAttribute("result", result);
        
        //확인화면으로 
        return "confirm";
    }

컨트롤러에 위와 같이 요청 핸들러 메서드를 추가한다.

위의 3번째 줄처럼 @Validated를 부여하면 유효성 검사가 되고, 실행한 결과(에러 정보)는 BindingResult 인터페이스에 보관된다.

여기서 주의할 점!

항상 @Validated 어노테이션이 부여된 클래스가 BindingResult 인터페이스보다 먼저 인수로 사용되어야 한당!

 

06 뷰에 추가(입력화면)

뷰에 아래와 같이 에러 표시 처리를 추가한다.

    <!--에러표시-->
    <ul th:if="${#fields.hasErrors('*')}">
        <li th:each = "err:${#fields.errors('*')}" th:text="${err}"></li>
    </ul>

* 참고 - th:each는 반복을 위해 사용!

2번째 줄에서 에러가 있는지 판단한다.

3번째 줄에서 에러 메시지를 배열로 돌려준다.

* 주의:<form>태그 안에 넣어줘야함!!!

 

07 뷰 생성(확인 화면)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>확인 화면</title>
</head>
<body>
    <h2>계산 결과</h2>
    <h3>[[${calcForm.leftNum}]] + [[${calcForm.rightNum}]] = [[${result}]]</h3>
</body>
</html>

 

08 확인


8-3 메시지 관리에 대해 알아보기

(1) 일반적인 메시지 관리

일반적으로 애플리케이션에서 표시하는 메시지는 프로그램들과 별도로 관리한다.

그 이유는 메시지를 템플릿에서 분리해서 메시지만 프로퍼티 파일로 관리하는 편이 유지 관리가 편하기 때문이다.

스프링 부트를 이용한 개발에서 유효성 검사에 대응하는 메시지는 ValidationMessages.properties로, 

그 외의 메시지는 messages.properties에 작성한다.

❗❗여기서 .properties 파일은 뭘까!?   (by 위키백과)
: 응용 프로그램의 구성 가능한 파라미터들을 저장하기 위해 
자바 관련 기술을 주로 사용하는 파일들을 위한 파일 확장자

 

(2) 스프링 부트에서 메시지 관리하기

01 messages.properties 생성

templates 폴더 아래에 messages.properties 파일을 생성한다.

'키=값' 형태로 프로퍼티를 정의한다.

객체 명은 소문자 카멜케이스로 표현한다. (CalcForm 클래스의 필드명을 항목으로 -> calcForm.필드명)

# entry 화면용
title.entry=입력화면
button.send=계산
# CalcForm용
calcForm.leftNum=왼쪽
calcForm.rightNum=오른쪽

아래와 같이 entry.html을 수정해주었다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <!--수정-->
    <title th:text="#{title.entry}">제목</title>
</head>
<body>
    <form th:action="@{/calc}" method="post" th:object="${calcForm}">
        <div>
            <input type="text" th:field="*{leftNum}">
            +
            <input type="text" th:field="*{rightNum}">
        </div>
        <!--수정-->
        <button type="submit" th:text="#{button.send}"></button>
        <!--에러표시-->
        <ul th:if="${#fields.hasErrors('*')}">
            <li th:each = "err:${#fields.errors('*')}" th:text="${err}"></li>
        </ul>
    </form>
</body>
</html>

 

02 ValidationMessages.properties 생성

templates 폴더 아래에 ValidationMessages.properties 파일을 생성한다.

# 단일 항목 검사용 메시지
javax.validation.constraints.NotNull.message={0}: 숫자를 입력해주세요.
org.hibernate.validator.constraints.Range.message={0}: {min}~{max} 범위의 숫자를 입력해주세요.
# 형변환 체크용 메시지
typeMismatch.java.lang.Integer={0}은 정수를 입력해주세요.

단일 항목 검사 어노테이션고 메시지에 대응하기 위해서는 어노테이션의 FQCN.message가 메시지를 취득하기 위한 '키'가 된다!!

여기서 FQCN(Fully Qualified Class Name)이란 '클래스가 속한 패키지명을 모두 포함한 이름'이라는 뜻이다.

 

03 Form 클래스 수정

아래와 같이 CalcForm 클래스를 수정했다.

@Data
public class CalcForm {

    @NotNull
    @Range(min=1, max=10)
    private Integer leftNum;

    @NotNull
    @Range(min=1, max=10)
    private Integer rightNum;

}

결과는 8-2와 동일하다.

 

유효성 검사의 메시지 취득 흐름에 대한 그림이 책에 아주 잘 나와있다!!


8-4 커스텀 유효성 검사기를 사용하는 프로그램 만들기

앞서 언급한 "스프링 프레임워크에서 제공하는 Validator 인터페이스를 구현하는 방법"으로 커스텀 유효성 검사기를 사용하는 프로그램을 만들어보겠다. 절차는 크게 다음과 같다.

  1. Validator인터페이스를 구현하는 커스텀 유효성 검사기를 생성.
  2. 컨트롤러에 앞에서 만든 유효성 검사기를 injection.
  3. WebDataBinder 인터페이스의 addValidators메서드로 커스텀 유효성 검사기를 등록하여 스프링 MVC에서 이용 가능하게 하기.

injection하는 과정에 대한 이해가 부족하다면 아래의 포스팅을 참고하자.

 

[Spring] chapter03 스프링 프레임워크의 핵심 기능 알아보기

아래 글은 스프링 프레임 워크 첫걸음 책을 기반하여 작성한 글입니다. 3-1 스프링 프레임워크의 핵심 기능 이 장에서는 chapter01에서 간단히 살펴본 DI와 AOP에 대해 자세히 다루겠다. (1) 의존성 주

na1-4an.tistory.com

 

01 Validator 구현 클래스 생성

package com.example.demo.validator;

import com.example.demo.form.CalcForm;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component //인스턴스 생성
public class CalcValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz){
        //인수로 전달받은 Form이 입력 체크의 대상인지 논리값으로 돌려줌.
        return CalcForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors){
        //대상 form을 취득
        CalcForm form = (CalcForm) target;

        //값이 입력되어 있는지 판단
        if(form.getLeftNum()!= null && form.getRightNum() != null){
            //왼쪽 입력값이 홀수이고 오른쪽 입력값이 짝수가 아닌경우
            if (!((form.getLeftNum() % 2 ==1) && (form.getRightNum() % 2 == 0))){
                //에러인 경우에는 Errors 인터페이스의 reject 메서드에
                // 에러 메ㅅ지의 키를 지정한다.
                errors.reject("com.example.demo.validator.CalcValidator.message");
            }
        }
    }
}

@Component 어노테이션을 클래스에 부여해서 인스턴스 생성 대상으로 지정한다.

위의 validate 메서드에서 인수로 전달된 Object 타입의 target을 CalcForm 타입으로 변환해서 커스텀 유효성 검사를 수행한다.

 

02 messages.properties에 추가

# 커스텀 유효성 검사용
com.example.demo.validator.CalcValidator.message=왼쪽에는 홀수를, 오른쪽에는 짝수를 입력해주세요.

 

03 컨트롤러에 injection(주입)

    @Autowired
    CalcValidator calcValidator;

 

04 커스텀 유효성 검사기 등록

    @InitBinder("calcForm")
    public void initBinder(WebDataBinder webDataBinder){
        webDataBinder.addValidators(calcValidator);
    }

💢이제 결과만 확인하면 되는데.. 자꾸

Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "button.send" (template: "entry" - line 14, col 31)

위와 같은 에러가 떴다. 나는 entry.html도 건드린적 없는데..

이는 SpringEL (Spring Expression Language) 표현식 "button.send"을 평가하는 동안 문제가 발생했음을 나타낸다.

이러한 유형의 오류는 Thymeleaf 템플릿 엔진에서 발생하는 것으로 보인다.

아마 커스텀 유효성 검사기를 추가하며 생긴 오류가 아닐까싶다.

그래서 우선, message.properties에 오류가 있나 확인을 했다.

 

흠 없었다..

그래서 챗선생의 도움ㅁ을 받기위해 물어봤지만 답을 주지 않았다.

하는 수 없이 코드 정독.!

원인은..

<button type="submit" th:text="#{button.send}"></button>

entry.html에서 위처럼 #이 들어가야하는 자리에 바보같이 $를 넣었던것..ㅎ