본문 바로가기
기초 및 언어/▶ Spring

05. Spring_유효성검사

by 류딩이 2025. 9. 21.

📌 Spring 유효성검사

🌈목차

1. Null / 비어있음 검사

2. 문자열 / 컬렉션 크기 제한

3. 숫자 값 제약

4. 정규식 검사

5. 날짜 / 시간 제약

6. 기타 (이메일, @AssertTrue / @AssertFalse)

7. 체크박스 유효성검증

8. Book - 유효성 검정 예제1 (수업 파일 Ex03)

9. Product - 유효성 검정 예제2 (수업 파일 Ex05)

10. Football - 유효성 검정 예제3 (수업 파일 Ex07)

 


❤️pom.xml에 삽입 필수

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

1. Null / 비어있음 검사

@NotNull null 금지 "", " " 허용
@NotEmpty null + 빈문자열 금지 " " 허용
@NotBlank null + 빈문자열 + 공백 금지 문자열 전용
@NotNull(message="값은 반드시 입력해야 합니다")
private String id;

@NotEmpty(message="비밀번호는 필수입니다")
private String password;

@NotBlank(message="이름은 공백일 수 없습니다")
private String name;

 

⬆ 목차로


2. 문자열 / 컬렉션 크기 제한

@Size(min, max) 문자열, 배열, List, Set의 길이 제한
@Size(min=3, max=10, message="아이디는 3~10자리여야 합니다")
private String userId;

 


 

⬆ 목차로

3. 숫자 값 제약

@Min(x) 최소값 이상
@Max(x) 최대값 이하
@Positive 양수만 허용 (0 불가)
@PositiveOrZero 0 이상
@Negative 음수만 허용
@NegativeOrZero 0 이하
@Digits(integer, fraction) 정수/소수 자릿수 제한
@DecimalMin(x), @DecimalMax(x) 실수 범위 제한
@Min(18) @Max(100)
private int age;

@Digits(integer=5, fraction=2) // 최대 99999.99
private BigDecimal price;

 

 

⬆ 목차로


4. 정규식 검사

@Pattern(regexp="정규식") 문자열이 특정 패턴과 일치하는지 검사

 

 

@Pattern(regexp="^[a-zA-Z0-9]+$", message="영문자와 숫자만 입력 가능합니다")
private String userId;

 

 

5. 날짜 / 시간 제약

@Past 과거 날짜만 허용
@PastOrPresent 과거 또는 오늘까지 허용
@Future 미래 날짜만 허용
@FutureOrPresent 오늘 또는 미래 날짜 허용

 

@Past(message="생일은 과거 날짜여야 합니다")
private LocalDate birthDate;

 

 

⬆ 목차로


6. 기타 

@Email 이메일 형식 검증
@AssertTrue true만 허용 (체크박스 동의 등)
@AssertFalse false만 허용

 

 

⬆ 목차로


7. 체크박스 유효성검증

 

  • 단일 체크박스 동의 → @AssertTrue
  • 복수 체크박스 최소 선택 → @Size(min=1)

 

 

1) 단일 체크박스 예시 (동의 여부) 

 

  • boolean 필드로 선언
  • @AssertTrue 를 붙이면 true(체크 O)일 때만 통과합니다.
public class UserForm {
	
    // 1)NotEmpty 
 	@NotEmpty(message = "동의 누락")
    private String agree;

	// 2)AssertTrue
    @AssertTrue(message = "약관에 동의해야 합니다")
    private boolean agree;

    // getter/setter
}

 

📄 HTML

<form th:action="@{/submit}" th:object="${form}" method="post">
    <label>
        <input type="checkbox" th:field="*{agree}"> 약관에 동의합니다
    </label>
    <div th:if="${#fields.hasErrors('agree')}" th:errors="*{agree}"></div>

    <button type="submit">제출</button>
</form>

 

2) 복수 체크박스 (최소 1개이상 선택 필수)

 

Spring Validation에는 기본 제공 어노테이션이 없기 때문에 👉 @Size(min=1) 을 사용

public class UserForm {

    @Size(min = 1, message = "취미는 최소 1개 이상 선택해야 합니다")
    private List<String> hobbies;

    // getter/setter
}

 

📄 HTML

<form th:action="@{/submit}" th:object="${form}" method="post">

    <!-- 취미 선택 -->
    취미 선택: <br>
    <label><input type="checkbox" th:field="*{hobbies}" value="수영"> 수영</label>
    <label><input type="checkbox" th:field="*{hobbies}" value="요리"> 요리</label>
    <label><input type="checkbox" th:field="*{hobbies}" value="게임"> 게임</label>
    <div th:if="${#fields.hasErrors('hobbies')}" th:errors="*{hobbies}"></div>
    <br><br>

    <button type="submit">제출</button>
</form>

 

👉 체크박스를 하나도 안 고르면 검증 실패

 

✅Controller 예시

@Controller
public class UserController {

    @GetMapping("/form")
    public String form(Model model) {
        model.addAttribute("form", new UserForm());
        return "form";
    }

    @PostMapping("/submit")
    public String submit(@Valid @ModelAttribute("form") UserForm form,
                         BindingResult result) {
        if (result.hasErrors()) {
            return "form"; // 유효성 실패 → 다시 입력 폼
        }
        return "success"; // 성공 시 이동할 뷰
    }
}

 

⬆ 목차로


8. Book - 유효성 검정  예제1

🔽BookBean.java 

더보기
package com.example.Ex02;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;

import java.util.List;
@Setter
@Getter
public class BookBean {
    @NotBlank(message ="제목 입력 누락")
    private String title;

    //@Size(min=3, max=5, message = "3~5자리 이하로 입력")
    @NotEmpty(message ="저자 입력 누락")
    @Length(min=5, message = "5글자 이상 입력")
    private String author;
    
    @Pattern(regexp = "^[0-9]+$", message = "price는 숫자로 입력하세요")
    @NotEmpty(message = "price 누락")
    private String price;
    
    @NotEmpty(message = "출판사 입력 누락")
    private String publisher;

    @NotEmpty(message = "서점 입력 누락")
    private List<String> bookstore;

    // ---- getter & setter ----
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }

    public String getPrice() { return price; }
    public void setPrice(String price) { this.price = price; }

    public String getPublisher() { return publisher; }
    public void setPublisher(String publisher) { this.publisher = publisher; }

    public List<String> getBookstore() { return bookstore; }
    public void setBookstore(List<String> bookstore) { this.bookstore = bookstore; }
}

🔽BookController.java

더보기
package com.example.Ex02;

import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class BookController {

	// 📌 1. 입력 화면 보여주기
    @RequestMapping(value = "bookform")
    public String book(Model model){
        BookBean bb = new BookBean();	// 빈 객체 생성
        model.addAttribute("book", bb);	// 모델에 "book" 이름으로 담음
        return "book/form";				// → templates/book/form.html 이동
    }
    
    // 📌 2. 입력값 처리
    @RequestMapping(value = "bookProc")
    // @ModelAttribute("book") 별칭과 form.html의 th:object="${book}"와 반드시 같아야함
    public String book2(@ModelAttribute("book") @Valid BookBean bb, BindingResult br){
        
        System.out.println("br.hasErrors(): " + br.hasErrors());
        String page="";
        if(br.hasErrors()){
            page="book/form"; // 에러 발생 → 다시 입력폼
        }else{
            page="book/result"; // 정상 입력 → 결과 페이지
        }
        return page;
    }


}

🔑  BookController  핵심 포인트

  • bookProc → 입력값 검증 후 에러 있으면 다시 form.html, 없으면 result.html
  • @ModelAttribute("book") 이름 = th:object="${book}" 와 반드시 일치해야 함
  • @Valid : BookBean 안의 유효성 검사 실행
  • BindingResult : 에러 여부 저장

🔽form.html

더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>


    <style>
        .err{
        font-size:9pt;
        color:red;
        }
    </style>

</head>
<body>
        book/form.html<br>
        <form th:action="@{bookProc}" th:object="${book}" method="post">
            책제목 : <input th:field="*{title}"><br>
            <div th:if="${#fields.hasErrors('title')}"
                 th:errors="*{title}" class="err"></div><br>

            저자 : <input th:field="*{author}"><br>
            <div th:if="${#fields.hasErrors('author')}"
                 th:errors="*{author}" class="err"></div><br>

            가격 : <input th:field="*{price}"><br>
            <div th:if="${#fields.hasErrors('price')}"
                 th:errors="*{price}" class="err"></div><br>

            출판사  : <input th:field="*{publisher}"><br>
            <div th:if="${#fields.hasErrors('publisher')}"
                 th:errors="*{publisher}" class="err"></div><br>

            구입가능 서점:
            <input type="checkbox" th:field="*{bookstore}" value="교보문고">교보문고
            <input type="checkbox" th:field="*{bookstore}" value="알라딘">알라딘
            <input type="checkbox" th:field="*{bookstore}" value="yes24">yes24
            <div th:if="${#fields.hasErrors('bookstore')}"
                 th:errors="*{bookstore}" class="err"></div>
            <input type="submit" value="전송">
        </form>

</body>
</html>

🔑 form.html  핵심

  • th:object="${book}" → 컨트롤러에서 전달된 BookBean과 연결
  • th:field="*{title}" → BookBean의 title 필드와 input 매핑
  • #fields.hasErrors('필드명') → 해당 필드에 에러가 있는지 확인
  • th:errors="*{필드명}" → BookBean에 정의된 유효성 메시지 출력

🔽result.html

더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    title : <span th:text="${book.title}"></span><br>
    author : <span th:text="${book.author}"></span><br>
    price : <span th:text="${book.price}"></span><br>
    publisher : <span th:text="${book.publisher}"></span><br>
    bookstore : <span th:text="${book.bookstore}"></span><br>
    <span th:each ="store : ${book.bookstore}" th:text="${store}">
        <br>

    </span>
    
</head>
<body>

 

✅ 정리

  • th:object="${book}" : BookBean과 연결 (폼 입력값 ↔ 객체 바인딩)
  • th:field="*{필드} : BookBean의 필드와 input 자동 매핑
  • @Valid + BindingResult : 검증 실행 + 결과 저장
  • th:errors : 해당 필드에 대한 에러 메시지 출력

 

 

 

 

⬆ 목차로

 


9. Product-유효성 검정 예제2 

🔽ProductBean.java

더보기
package com.example.Ex02;

import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

import java.util.List;
public class ProductBean {

    @NotBlank(message = "아이디 입력 누락")
    private String id;

    @Size(min=3, max=5 , message = "3~5자리 이하로 입력하세요.")
    @NotBlank(message = "비밀번호 입력 누락")
    private String passwd;

    @Size(min=1, message = "하나 이상 선택하세요")
    private List<String> product;

    @NotBlank(message = "배송시간 누락")
    private String deliveryTime;

    @NotBlank(message = "결제방법 선택 누락")
    private String radio;

    @AssertTrue(message = "동의 누락")
    private boolean agree;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getPasswd() { return passwd; }
    public void setPasswd(String passwd) { this.passwd = passwd; }

    public List<String> getProduct() { return product; }
    public void setProduct(List<String> product) { this.product = product; }

    public String getDeliveryTime() { return deliveryTime; }
    public void setDeliveryTime(String deliveryTime) { this.deliveryTime = deliveryTime; }

    public String getRadio() { return radio; }
    public void setRadio(String radio) { this.radio = radio; }

    public boolean isAgree() { return agree; }   // ✅ boolean getter는 isAgree
    public void setAgree(boolean agree) { this.agree = agree; }
}

🔽Controller.java

더보기
package com.example.Ex02;

import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class Con {

    @GetMapping(value = "/")
    public String home(){
        return "home";
    }

    // 폼 띄우기
    @GetMapping(value = "/form")
    public String form(Model model){
        ProductBean pb = new ProductBean();
        model.addAttribute("prod", pb);

        return "form";
    }

    // 입력값 처리
    @PostMapping(value = "/submit")
    public String proc(@ModelAttribute("prod") @Valid ProductBean pb, BindingResult br){

        String page ="";
        if(br.hasErrors()){ // 에러가 있다면?
            page ="/form";
        }else{
            page="result";
        }
        return page;
    }
}

🔽form.html

더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>form</title>
</head>
<style>
    .err {
        color: red;
    }
</style>


<body>
    <h2>마트 상품 구매 내역</h2>
    <form th:action="@{/submit}" th:object="${prod}" method="post">
        <label>아이디 </label> 
            <input type="text" th:field="*{id}"><br>
            <div th:if="${#fields.hasErrors('id')}"
        		 th:errors="*{id}" class="err"></div>
        <br>
        <label>비번 </label> 
            <input type="text"  th:field="*{passwd}"><br>
            <div th:if="${#fields.hasErrors('passwd')}"
                 th:errors="*{passwd}" class="err"></div>
        <br>
        구매상품:
        <input type="checkbox" th:field="*{product}" value="식품">식품
        <input type="checkbox" th:field="*{product}" value="의류">의류
        <input type="checkbox" th:field="*{product}" value="도서">도서
        <input type="checkbox" th:field="*{product}" value="가구">가구
        <br>
        <div th:if="${#fields.hasErrors('product')}"
             th:errors="*{product}" class="err"></div><br>
        배송시간:
        <select th:field="*{deliveryTime}">
            <option value="">선택</option>
            <option value="오전">오전 (09:00 ~ 12:00)</option>
            <option value="오후">오후 (12:00 ~ 18:00)</option>
            <option value="저녁">저녁 (18:00 ~ 21:00)</option>
        </select>
        <br>
        <div th:if="${#fields.hasErrors('deliveryTime')}"
             th:errors="*{deliveryTime}" class="err"></div>
        <br>
        결제방법 :
        <input type="radio" th:field="*{radio}" value="카드">카드
        <input type="radio" th:field="*{radio}" value="핸드폰">핸드폰
        <br>
        <div th:if="${#fields.hasErrors('radio')}"
             th:errors="*{radio}" class="err"></div><br>
        결제동의
        <input type="checkbox" th:field="*{agree}" value="동의"><br>
        <div th:if="${#fields.hasErrors('agree')}"
             th:errors="*{agree}" class="err"></div><br>
        <br>
        <button type="submit">제출</button>
    </form>
</body>
</html>

🔽result.html

더보기
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
        result.html<br><br>
        아이디 : <span th:text="${prod.id}"></span><br>
        비번 : <span th:text="${prod.passwd}"></span><br>
        구매상품 : <span th:each="p,stat : ${prod.product}"
                th:text="${p + (stat.last? '':',')}"></span><br>


        배송시간 : <span th:text="${prod.deliveryTime}"></span><br>
        결제 방법 : <span th:text="${prod.radio}"></span><br>
        동의 : <span th:text="${prod.agree ? '동의':'미동의'}"></span><br>
</body>
</html>

 

 

⬆ 목차로

 


10. Football - 유효성 검정 예제3 (수업 파일 Ex07)

 

🔽FootballDto.java

더보기
package com.example.Ex02.dto;

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

import java.util.List;

public class FootballDto {
    private int num;

    @NotBlank(message = "아이디 누락")
    private String id;

    @NotBlank(message = "비밀번호 누락")
    private String pw;

    // 라디오 버튼 → 단일 선택
    @NotBlank(message = "우승 예상 국가 누락")
    private String win;

    // 체크박스 → 다중 선택
    @Size(min=1, message = "16강 예상 국가 누락")
    private List<String> round16;

    public int getNum() { return num; }
    public void setNum(int num) { this.num = num; }

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getPw() { return pw; }
    public void setPw(String pw) { this.pw = pw; }

    public String getWin() { return win; }
    public void setWin(String win) { this.win = win; }

    public List<String> getRound16() { return round16; }
    public void setRound16(List<String> round16) { this.round16 = round16; }

}

🔽FootballContriller.java

더보기
package com.example.Ex02;


import com.example.Ex02.dto.FootballDto;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class FootballController {
    @GetMapping(value = "/write")
    public String write(@ModelAttribute("fb") FootballDto FDto){

        return "write";
    }

    @PostMapping(value = "/writeProc")
    public String proc(@ModelAttribute("fb") @Valid FootballDto fb, BindingResult rs){

        String page="";
        if(rs.hasErrors()){
            page="write";
        }else{
            page="result";
        }
        return page;
    }

}

🔽write.html

더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>wrtie</title>
</head>

<style>
    .err { color: red; font-size: 12px; }
</style>
<body>
    <h3>wrtie.html</h3>

    <form th:action="@{/writeProc}" th:object="${fb}" method="post">
    <table border="1">
        <tr>
            <td>아이디</td><td>
            <input type="text" th:field="*{id}">
            <div th:if="${#fields.hasErrors('id')}"
                 th:errors="*{id}" class="err"></div>
        </td>

        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="text" th:field="*{pw}">
                <div th:if="${#fields.hasErrors('pw')}"
                     th:errors="*{pw}" class="err"></div>
            </td>
        </tr>

        <tr>
        <td>우승 예상 국가</td>
            <td>
                <input type="radio" th:field="*{win}" value="한국">한국
                <input type="radio" th:field="*{win}" value="미국">미국
                <input type="radio" th:field="*{win}" value="독일">독일
                <input type="radio" th:field="*{win}" value="스페인">스페인
                <div th:if="${#fields.hasErrors('win')}"
                     th:errors="*{win}" class="err"></div>
            </td>

        </tr>

        <tr>
            <td>16강 예상 국가</td>
            <td>
                <input type="checkbox" th:field="*{round16}" value="한국">한국
                <input type="checkbox" th:field="*{round16}" value="멕시코">멕시코
                <input type="checkbox" th:field="*{round16}" value="독일">독일
                <input type="checkbox" th:field="*{round16}" value="브라질">브라질
                <input type="checkbox" th:field="*{round16}" value="스위스">스위스
                <input type="checkbox" th:field="*{round16}" value="잉글랜드">잉글랜드
                <div th:if="${#fields.hasErrors('round16')}"
                     th:errors="*{round16}" class="err"></div>
            </td>

        </tr>

        <tr>
            <td colspan="2">
                <input type="submit" value="입력">
                <a href="@{list}"> 목록보기</a>
            </td>
        </tr>

    </table>
    </form>

</body>
</html>

 

🔽result.html

 

더보기
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>result</title>
</head>
<body>
    result.html<br>
    <table border="1">
        <tr>
            <td>아이디</td><td th:text="${fb.id}"></td>
        </tr>
        <tr>
            <td>비밀번호</td><td th:text="${fb.pw}"></td>
        </tr>
        <tr>
            <td>우승 예상 국가</td><td th:text="${fb.win}"></td>
        </tr>
        <tr>
            <td>16강 예상 국가</td><td th:text="${fb.round16}"></td>
        </tr>

    </table>
</body>
</html>

⬆ 목차로