08장 유효성 검사 기능

박준우·2025년 7월 7일

Spring Boot

목록 보기
8/14

1. 입력 체크 알아보기

(1) 유효성 검사란?

유효성 검사는 사용자가 입력한 ㄷ데이터가 올바른 형식인지 검사하는 것을 의미한다.
유효성 검사는 단일항목 검사, 상관항목 검사로 나뉜다.

단일 항목 검사: 입력 항목 하나 하나에 대해 입력을 체크한다.
이를 위해서는 Form클래스 등 필드에 에너테이션을 부여한다.
주로, 자바 Bean validation 또는, 하이버네이트 프레임워크의 Hibernate Validator 를 사용한다.

상관항목검사 : 입력 필드 1개가 아닌, 여러필드를 조합해서 입력을 체크한다.
주로, 3가지 방법을 사용한다.

  1. @AssertTrue 사용
  2. Bean Validation 사용
  3. 스프링 프레임워크인 Validator 인터페이스의 구현

Bean Validation 에너테이션

에너테이션설명사용예시
@NotNull값이 Null인지 확인한다.@NotNull
private String name
@NotEmpty문자열, 컬렉션이 비어있는지 확인@NotEmpty
private String name
@NotBlank문자열이 비어있는지 확인@NotBlank
private String name
@Min수치가 지정된 최솟값 이상인지 체크@Min(19)
private int age
@Max수치가 지정된 최대값 이하인지 체크@Max(100)
private String name
@Size문자열, 배열, 컬렉션 크기가 지정범위 내에 있는지 확인@Size(min=1, max=10)
private String name
@Pattern문자열이 지정된 표현식과 일치하는지 확인@Pattern(regexp="^[a-zA-Z]+$")
private String name
@Email문자열이 이메일 형식인지 확인@Email
private String email
@Positive숫자가 양수인지 확인@Positive
private int value
@PositiveOrZero숫자가 양수 또는 0인지 확인@Positive
private int value
@Negative숫자가 음수인지 확인@Negative
private int value
@NegativeOrZero숫자가 음수 또는 0인지 확인@NegativeOrZero
private int value

Hibernate Validator 에너테이션

에너테이션설명사용예시
@Length문자열크기가 지정범위 내에 있는지 확인@Length(min=1, max=10)
private String name
@Range수치가 지정된 범위 내에 있는지 확인@Range(min=1, max=10)
private int value
@CreditCardNumber유효한 신용카드 번호인지 확인@CreditCardNumber
private String creditCard

이 외에도 다양한 입력값 검사 에너테이션이 존재한다.

Null관련 에너테이션

에너테이션설명
@NotNullnull일 경우 오류, (빈 문자, 공백 허용)
@NotEmptynull일 경우 오류, (빈 문자 오류, 공백 허용)
@NotBlanknull일 경우 오류, (빈 문자 오류, 공백 오류)

2. 단일 항목 검사 프로그램

(0) 사전 프로그램 세팅

의존성 추가
1. Spring Boot DevTools
2. Thymeleaf
3. Spring Web
4. Lombok
5. Validation(입출력 유효성 검사 의존성)

빌드 세팅
1. 설정-빌드도구-gradle-gradle jvm에서 java버전 변경하기.
2. 프로젝트 구조- 프로젝트설정-프로젝트SDK에서 java버전 변경하기.

(1) Form 클래스 생성

폼 클래스란? 웹페이지(뷰)에서 사용자에게 데이터를 전송하기 위한 입력양식 처리에 사용되는 클래스로, 주로 컨트롤러에서 이 클래스를 이용한다.

@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;
}
#### 해석
@Data: 자동으로 getter, setter 등 생성해준다. 
leftNum, rightNum 두개의 변수는 각가 두개의 입력값 검사를 거친다.

각각 1~10 사이어야 한다.

(2) 컨트롤러 생성

@Controller
public class ValidationController {
    /** '폼 연동 빈' 초기화 */
    @ModelAttribute
    public CalcForm setUpForm() {
        return new CalcForm();
    }

    /** 입력 화면 표시 */
    @GetMapping("show")
    public String showView() {
        // 반환값으로 뷰 이름을 반환
        return "entry";
    }
}
#### 해석
폼 연동 빈 초기화란? 
@ModelAttribute와 return값으로, Form 클래스를 선언한 함수를 만들면 되며

입력 페이지가 열렸을 때, 컨트롤러에서 폼과 연동할 객체(Calc Form)을 미리 만들어 모델에 저장해 놓았다가,
출력 뷰에 전달하는 역할을 한다. (단, 폼 연동 빈은 일시적 저장이다.)

왜 이렇게 하는가?
기존방식은 데이터를 입력에 성공했을 땐 문제가없지만, 성공하지 못하면 객체가 만들어지지 않아, 
입력값검사나, 에러 메시지를 자동으로 보여줄 수 없다.
이렇게 입력 페이지가 열리자 마자 CalcForm객체를 모델에 저장해둔다면, 편하게 객체검증이 가능하다. 


(3) 입력 뷰 생성

<!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:action="@{/calc}" method="post" : 이 폼은 /calc URL로 post방식으로 보낸다.
th:object="${calcForm} : 이 폼은 calcForm객체와 바인딩 됨을 선언한다.

(4) 컨트롤러 내용 추가

@Controller
public class ValidationController {
	/** '폼 연동 빈' 초기화 */
	@ModelAttribute
	public CalcForm setUpForm() {
		return new CalcForm();
	}

	/** 입력 화면 표시 */
	@GetMapping("show")
	public String showView() {
		// 반환값으로 뷰 이름을 반환
		return "entry";
	}
	
	// ▽▽▽▽▽ 예제 8.4 ▽▽▽▽▽
	/** 확인 화면 표시: Form 클래스 사용 */
	@PostMapping("calc")
	public String confirmView(@Validated CalcForm form,
	        BindingResult bindingResult, Model model) {
	    // 입력 체크
	    if (bindingResult.hasErrors()) { //에러를 가지고 있다면,
	        // 입력 오류
	        // 입력 화면으로 되돌아간다. 
	        return "entry"; 
	    }
	    // 입력 오류 없음
		// 덧셈 실행
	    Integer calcResult = form.getLeftNum() + form.getRightNum();
	    // Model에 저장
	    model.addAttribute("result", calcResult);
	    // 확인 화면으로 이동
	    return "confirm"; 
	}
	// △△△△△ 예제 8.4 △△△△△
}
#### 해석
첫 컨트롤러에 예제 8.4를 추가하였다.
@PostMapping("calc"): calc URL로 데이터가 Post방식으로 들어왔을 때, 
@Validated CalcForm form : 입력뷰에서, th:object="${calcForm} 를 통해 폼의 데이터가
CalcForm 객체로 바인딩되고, @Validated를 통해 입력값 검사 조건이 적용된다.

BindingResult bindingResult: 검증 결과가 이 객체에 저장된다. 
bindingResult.hasError() : 를 통해 에러를 검사받는다. 

model.addAttribute("result", calcResult) : 더한 결과를 담은 객체를 result라는 모델명으로 
모델에 추가한다.
	  

(5) 입력 뷰 내용 추가

<!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="계산">
		<!-- ▽▽▽▽▽ 예제 8.5 ▽▽▽▽▽ -->
		<!-- 오류 표시 -->
		<ul th:if="${#fields.hasErrors('*')}">
			<li th:each="err:${#fields.errors('*')}" th:text="${err}"></li>
		</ul>
		<!-- △△△△△ 예제 8.5 △△△△△ -->
	</form>
</body>
</html>

#### 해석
ul th:if= : 만약, 어떤 경우라도, 에러가 있으면 ul이 보여진다. 
th:each="err:${#fields.errors('*')} : 모든 에러사항을 출력 하기 위한 반복문이다.
#fields는 타입리프가 제공하는 필드와 관련된 도움을 주는 객체이다. 

(6) 출력 뷰 만들기

<!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>

#### 해석
모델에 저장된 데이터를 각각 출력한다.
calc.Form(leftNum,rightNum) + result 출력, 

정리하기

  1. 사용자가 /show로 get을 요청하면,
  2. showView() 메소드가 실행되며, 컨트롤러는 입력 View.html의 이름(Entry)을 리턴한다.
  3. 컨트롤러가 입력화면을 띄우기전, @ModelAttribute이 실행되며, CalcForm 클래스의 객체가 생성된다.
  4. 컨트롤러가 만든 CalcForm 객체를 th:object="${calcForm} 으로, Entry.html에게 넘겨준다. (+ th:field = 로 객체에 데이터 삽입)
    ㄴ 모델에 들어간 객체 이름 첫글자는 자동으로 소문자가 된다.
  5. 사용자가 숫자를 입력하고, 계산버튼을 누르면 Post방식으로, calcform(leftNum, rightNum) 데이터가 /calc 로 전송된다.
  6. 이 때 컨트롤러에서 폼의 입력값을 받아서, 비지니스 로직을 처리한다. (유효성검사, 모델저장, 계산)
  7. 모델을 이용해 출력뷰가 출력한다.

결과( 정상 범위 값을 입력)

결과(오류 범위 값을 입력)

입력 체크 함수(bindingResult.hasErrors()) 결과가 return Entry 임으로, 입력 화면(Entry)로 돌아간다.

@ModelAttribute 자세히 알아보기

@ModelAttribute가 포함된 에너테이션은 요청 메서드가 호출되기 전에 자동으로 실행된다.

@Controller
public class MyController {

	@ModelAttribute("message")
	public String setupMessage() {
		return "Hello, World!";
	}

	@GetMapping("/greet")
	public String greet() {
		return "greeting-page";
	}
}

여기서, GetMapping전에, message라는 이름으로, return결과인 Hello, World가 모델에 저장된다.

만약 메서드의 인수에 이 에너테이션을 사용하면, 요청파라미터에서 자동으로 바인딩된 객체를 얻는다.

@Controller
public class MyController {
	
	@RequestMapping("/result")
	public String submitForm(@ModelAttribute("userForm") User user) {
		// 폼에서 전송된 데이터는 User 객체에 바인딩됨
		System.out.println("사용자명: " + user.getUserName());
		return "result-page";
	}
}

쉽게 말하자면, 요청 파라미터와, User 클래스의 필드명이 일치하면, Spring이 자동으로 User객체를 만들어줘 필드에 값을 자동으로 넣어준다. (=자동 바인딩)
따라서, 컨트롤러의 User 객체에 값이 자동으로 담기게 된다.

1. 요청 파라미터 = (Username, age)
<form action="/result" method="post">
    <input name="userName" type="text">
    <input name="age" type="text">
    <button type="submit">제출</button>
</form>

2. User 클래스 구성
public class User {
    private String userName;
    private int age;

    // getter, setter
}

3. 컨트롤러 
@Controller
public class MyController {
	
	@RequestMapping("/result")
	public String submitForm(@ModelAttribute("userForm") User user) {
		// 폼에서 전송된 데이터는 User 객체에 바인딩됨
		System.out.println("사용자명: " + user.getUserName());
		return "result-page";
	}
}

#### 만약 자동 바인딩을 사용하지 않았을 경우 아래처럼 파라미터를 직접 받아야한다. 
@RequestMapping("/result")
public String submitForm(@RequestParam("userName") String userName,
                         @RequestParam("age") int age) {
    User user = new User();
    user.setUserName(userName);
    user.setAge(age);

    // 이후 user 객체 사용
    return "result-page";
}

이러한 특성 때문에 Form(입력 양식 저장한 태그, 여러개 데이터를 한번에 주고 받음)을 사용할 때 주로 사용한다.

메서드 인수에 @ModelAttribute를 사용하는 경우(생략형)

Spring은 클래스의 요청핸들러 메소드에 사용자 정의 데이터 타입(User 클래스 등)이 있는 경우 자동으로 해당타입의 객체를 생성하고, 요청 매개변수를 바인딩한다. 따라서, 아래처럼도 작동한다.

@Controller
public class MyController {
	
	@RequestMapping("/result")
	public String submitForm(User user) {
		// 폼에서 전송된 데이터는 User 객체에 바인딩됨
		System.out.println("사용자 이름: " + user.getUserName());
		return "result-page";
	}
}

다만, 이 경우, 클래스 첫문자가 소문자로 저장되어 모델명이 user가 된다.

3. 상관항목 검사 프로그램

(0) 사전 프로그램 세팅

의존성 추가
1. Spring Boot DevTools
2. Thymeleaf
3. Spring Web
4. Lombok
5. Validation(입출력 유효성 검사 의존성)

빌드 세팅
1. 설정-빌드도구-gradle-gradle jvm에서 java버전 변경하기.
2. 프로젝트 구조- 프로젝트설정-프로젝트SDK에서 java버전 변경하기.

(1) Form클래스 생성

@Data
public class SampleForm {
	/** 비밀번호 */
	private String password;
	/** 확인용 비밀번호 */
	private String confirmPassword;

	// 비밀번호와 확인용 비밀번호가 일치하는지 확인
	@AssertTrue(message = "비밀번호가 일치하지 않습니다")
	public boolean isSamePassword() {
		return Objects.equals(password, confirmPassword);
	}
}

#### 해석
@Data: Getter Setter를 자동으로 만들어준다.

상관항목 검사를 하기 위해 반드시 @AssertTrue 애너테이션과 is로 시작하는 클래스를 만들어야한다. 
@AssertTrue는 is, get, set등을 뺀 남은 부분의 첫글자를 소문자로 바꾼뒤 이것을 
변수명으로 만들고, 이렇게 만들어진 변수명으로 에러를 처리한다. 
즉, samePassword 변수가 


@AssertTrue(message = "비밀번호가 일치하지 않습니다") : bool 타입을 리턴하는 메소드와
같이 사용하며, 
true면 통과, false면 에러가 발생하고, 그 에러 메세지를 지정한 문구로 바꾼다.

(2) 컨트롤러 만들기

@Controller
public class CheckController {
	// 입력 화면 표시
	@GetMapping()
	public String showForm(SampleForm form) {
		return "entry";
	}

	// 상관 항목 검사 실행
	@PostMapping
	public String check(@Validated SampleForm form,
			BindingResult bindingResult, Model model) {
		// 유효성 검사 수행
		if (bindingResult.hasErrors()) {
			return "entry";
		}
		model.addAttribute("message", "입력에 문제가 없습니다");
		return "result";
	}
}
#### 해석
showForm에서 @Validated는 생략형이 되어,  
SampleForm 타입의 객체를 동적 바인딩 하여 인수로 받는다. 
따라서, 모델명은 sampleForm이 된다.


(3) 입력 뷰 만들기

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>상관 항목 검사</title>
</head>
<body>
<h1>@AssertTrue 활용</h1>
<form th:action="@{/}" th:object="${sampleForm}" method="post">
  <p>비밀번호     <input type="password" th:field="*{password}" /></p>
  <p>확인용 비밀번호 <input type="password" th:field="*{confirmPassword}" /></p>
  <!-- 상관 항목 검사 오류 메시지 -->
  <p th:if="${#fields.hasErrors('samePassword')}"
     th:errors="*{samePassword}" style="color: red;">
    상관 항목 검사 오류
  </p>
  <p><input type="submit" value="확인" /></p>
</form>
</body>
</html>

#### 해석
<form th:action="@{/}" th:object="${sampleForm}" method="post"> :sampleForm 모델(생략형 앞글자 소문자)
을 가져와, post방식으로 전송한다. 

th:if="${#fields.hasErrors('samePassword')}" : #fields는 타입리프가 제공하는 필드관련 객체
.hasError('samePassword'): fields.samePassword라는 필드에 검증에러가 있는지 체크한다.
만약 있다면,
해당 필드에서 발생한 오류메시지를 th:error=*{samePassword}를 통해서 출력한다. 


(4) 출력 뷰 만들기

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>상관 항목 검사</title>
</head>
<body>
<h1 th:text="${message}">메시지</h1>
</body>
</html>

결과(입력에 이상이 없을 때)


결과(입력에 이상이 있을 때)

profile
DB가 좋아요

0개의 댓글