Backend/๐ŸŒฑ Spring

[Spring] Bean Validation

HS0601 2025. 7. 28. 19:53
Bean Validation๋ž€?
์ž…๋ ฅ๋œ ๊ฐ’์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ๋„๊ตฌ
ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ํ˜•์‹์ด ์ž˜๋ชป๋๊ฑฐ๋‚˜ ๋ฒ”์œ„์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ๋ฐฑ์—”๋“œ๊ฐ€ ๊ฑธ๋Ÿฌ๋‚ด๊ณ  ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ๋ ค์ค˜์•ผ ํ•จ

 

 

์™œ ํ•„์š”ํ•œ๊ฐ€

 

  • ๊ฒ€์ฆ์„ ์•ˆํ•˜๋ฉด?

์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์ด DB์— ์ด์ƒํ•˜๊ฒŒ ๋“ค์–ด๊ฐ€๊ฑฐ๋‚˜

์„œ๋ฒ„๊ฐ€ ์•„์˜ˆ ํ„ฐ์ง€๊ฑฐ๋‚˜

์˜ˆ์™ธ(์—๋Ÿฌ)๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ์•ฑ์ด ๋จนํ†ต์ด ๋จ

 

  • ๊ฒ€์ฆํ•˜๋ฉด?

์ž˜๋ชป๋œ ์ž…๋ ฅ์€ ๋ฏธ๋ฆฌ ๊ฑธ๋Ÿฌ์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ

์„œ๋ฒ„๋Š” ์•ˆ์ „ํ•˜๊ฒŒ ์ž‘๋™๋จ

 

Bean Validation ์ ์šฉ ํ๋ฆ„

 

์ž…๋ ฅ๊ฐ’ -> DTO ๊ฐ์ฒด๋กœ ๋ฐ”์ธ๋”ฉ๋จ
-> ๊ฒ€์ฆ ์–ด๋…ธํ…Œ์ด์…˜(@NotBlank ๋“ฑ)์ด ๋ถ™์—ฌ์žˆ์Œ
-> ์Šคํ”„๋ง์ด ์ž๋™์œผ๋กœ ๊ฒ€์‚ฌ
-> ์˜ค๋ฅ˜ ๋‚˜๋ฉด BindingResult์— ๋‹ด๊น€
-> Controller์—์„œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

 

์˜ˆ์ œ์ฝ”๋“œ ๋ฐ”๋กœ๊ฐ€์žฃ~

 

1) DTO๋งŒ๋“ค๊ธฐ

// SignUpRequestDto.java
package com.example.demo.dto;

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

@Getter
public class SignUpRequestDto {
	// null, "", " " ์ „๋ถ€ ์•ˆ ๋จ
    @NotBlank(message = "์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") 
    private String name;
	// null์ด๋ฉด ์•ˆ ๋จ
    @NotNull(message = "๋‚˜์ด๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") 
     // ์ˆซ์ž ๋ฒ”์œ„ ์ œํ•œ
    @Range(min = 1, max = 120, message = "๋‚˜์ด๋Š” 1~120 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    private Integer age;
}

ํ•„์š”ํ•œ ์ด์œ ๋Š”

ํด๋ผ์ด๋„ˆํŠธ๊ฐ€ ๋นˆ ๊ฐ’์ด๋‚˜ ์ž˜๋ชป๋œ ๊ฐ’์„ ๋ณด๋‚ด๋„ ์šฐ๋ฆฌ๊ฐ€ if๋ฌธ์œผ๋กœ ๋งค๋ฒˆ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ๊ฒ€์‚ฌํ•ด์คŒ

 

 

 

2) Controller ๋งŒ๋“ค๊ธฐ

// BeanValidationController.java
package com.example.demo.controller;

import com.example.demo.dto.SignUpRequestDto;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;

@Slf4j
@Controller
public class BeanValidationController {

    @PostMapping("/signup")
    public String signup(
		    //  @Valid → ์ด DTO๋ฅผ ์ž๋™๊ฒ€์ฆ
            @Valid SignUpRequestDto requestDto,  
             //  ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด (๋ฌด์กฐ๊ฑด DTO ๋‹ค์Œ์— ์œ„์น˜)
            BindingResult bindingResult, 
            //  Thymeleaf ํ…œํ”Œ๋ฆฟ์— ๋ฐ์ดํ„ฐ ๋„˜๊ธฐ๊ธฐ์šฉ
            Model model  
    ) {
        if (bindingResult.hasErrors()) {
            //  ๊ฒ€์ฆ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์—ฌ๊ธฐ๋กœ ์˜ด
            log.info("์—๋Ÿฌ ๋ฐœ์ƒ: {}", bindingResult.getAllErrors());
             // error.html ๋ฐ˜ํ™˜
            return "error";
        }

        // ์œ ํšจ์„ฑ ํ†ต๊ณผํ•œ ๊ฒฝ์šฐ
        model.addAttribute("name", requestDto.getName());
        model.addAttribute("age", requestDto.getAge());
		 // complete.html ๋ฐ˜ํ™˜
        return "complete";
    }
}

 

์™œ @Valid ๋ž‘ BindingResult๊ฐ€ ์žˆ์–ด์•ผ ํ• ๊นŒ?

  • @Valid๊ฐ€ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ BindingResult ๊ฐ€ ๋‹ด๊ธฐ ๋•Œ๋ฌธ
  • ์˜ค๋ฅ˜๊ฐ€ ์žˆ์œผ๋ฉด if(bindingResult.hasError())์กฐ๊ฑด๋ฌธ์ด true๊ฐ€ ๋ผ์„œ ์—๋Ÿฌ ํŽ˜์ด์ง€๋กœ ๋ณด๋ƒ„

 

3) ํ…œํ”Œ๋ฆฟ

<!-- complete.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ</title>
</head>
<body>
    <h1>ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต!</h1>
    <p>์ด๋ฆ„: <span th:text="${name}"></span></p>
    <p>๋‚˜์ด: <span th:text="${age}"></span></p>
</body>
</html>

 


 

๋นˆ ๊ฒ€์ฆ์ด ์ž‘๋™ํ•˜๋ ค๋ฉด ํ•„์š”ํ•œ ์กฐ๊ฑด

 

์กฐ๊ฑด ์„ค๋ช…
@Valid DTO์— ๋ถ™์–ด์„œ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๊ฒ€์ฆํ•˜๋ผ๊ณ  ์ง€์‹œ
BindingResult ๊ฒ€์ฆ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•จ. ์—†์œผ๋ฉด ์Šคํ”„๋ง์ด ์˜ˆ์™ธ ํ„ฐ๋œจ๋ฆผ
spring-boot-starter-validation ์ด ์˜์กด์„ฑ์ด ์žˆ์–ด์•ผ ์ „์ฒด ๊ฒ€์ฆ ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•จ
(Gradle์— ํฌํ•จ๋˜์–ด ์žˆ์–ด์•ผ ํ•จ)

 

์ฃผ์˜์‚ฌํ•ญ ์ •๋ฆฌ
  • @Valid ๋’ค์—๋Š” ๋ฌด์กฐ๊ฑด BindingResult ๋ถ™์–ด์•ผ ํ•จ -> ์•ˆ๊ทธ๋Ÿฌ๋ฉด ์—๋Ÿฌ๋‚จ
  • ๋ฐ”์ธ๋”ฉ์ด ์•ˆ ๋˜๋Š” ํƒ€์ž…(์˜ˆ: age์— ๋ฌธ์ž๊ฐ€ ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ)์€ @Valid์ ์šฉ ์ž์ฒด๊ฐ€ ์•ˆ๋จ-> BindingResult์— ์—๋Ÿฌ๋กœ๋งŒ ๋‚จ์Œ
  • DTOํ•„๋“œ๋งˆ๋‹ค ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ผญ ์จ์ค˜์•ผ ๊ฒ€์ฆ์ด ์ ์šฉ๋จ

 

 

ํ•ต์‹ฌ ๋ฌธ๋ฒ• ์ •๋ฆฌ

 

1) ์˜์กด์„ฑ ์ถ”๊ฐ€(build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-validation'

 

2) DTO์—์„œ ๊ฒ€์ฆ ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉํ•˜๊ธฐ

@Getter
public class SignUpRequestDto{
	@NotBlank(massage="์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") //null, ""," " ๋ถˆ๊ฐ€๋Šฅ
	private String name;

	@NotNull(message="๋‚˜์ด๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")//null ๋ถˆ๊ฐ€๋Šฅ
	@Range(min=1, max=120, message="๋‚˜์ด๋Š” 1~120 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.");
	private Integer age;

}
  • ๋ฌธ์ž์—ด์ด๋ฉด  @NotBlank
  • ์ˆซ์ž๋ฉด @NotNull
  • ๋ฒ”์œ„์ œํ•œ @Range

 

 

3) Controller์—์„œ ๊ฒ€์ฆ์ฒ˜๋ฆฌ ๋ฌธ๋ฒ•

@PostMapping("/signup")
public String signup{
	  @Valid SignUpRequestDto dto,        // 1. ๊ฒ€์ฆํ•  DTO
        BindingResult bindingResult,        // 2. ๊ฒ€์ฆ ๊ฒฐ๊ณผ ์ €์žฅ (์ˆœ์„œ ์ค‘์š”!)
        Model model                         // 3. Thymeleaf์— ๋ฐ์ดํ„ฐ ๋„˜๊ธฐ๊ธฐ
) {
		if(bindingResult.hasError){
			//์—๋Ÿฌ ์žˆ์œผ๋ฉด ์—ฌ๊ธฐ์„œ ์ฒ˜๋ฆฌ
			return "error";
		}
		//์ •์ƒ์ฒ˜๋ฆฌ
		model.addAttribute("name",dto.getName());
		model.addAttribute("age",dto.getAge());
	}
}