개발정리

스프링-유효성검사 본문

스프링/스프링 프레임워크

스프링-유효성검사

coffee. 2024. 5. 12. 19:00

스프링 유효성 검사

유효성 검사란 사용자가 요청한 데이터의 폼이 어던 조건에 충족하는 지 판단하는 과정입니다.

예를 들어 비밀번호에 특수문자가 포함되어야 한다거나 10자리 이상이어야한다는 조건을 들수 있습니다.

 

우리가 살펴볼 예제는 상품을 등록하는 예제입니다.

아이템의 필드로는 itemName,price,quantity가 있습니다.

 

우리는 itemName은 공백을 안넣게 하고

price는 1000원 이상이어야하며

quantity는 음수가 아닌 값을 받도록 유효성 검사를 진행 할 것 입니다.

 

@Data
@Entity
public class Item {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String itemName;
	private Integer price;
	private Integer quantity;
	
	public Item() {
		
	}
	
	public Item(String itemName,Integer price,Integer quantity) {
		this.itemName=itemName;
		this.price=price;
		this.quantity=quantity;
	}
}

우리가 사용할 item의 entity입니다.

 

스프링에서 특정 클래스에 대한 유효성을 검증하기 위해서는 validator라는 인터페이스를 구현 해야합니다.

import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.example.demo.Item;

@Component
public class ItemValidator implements Validator {

	@Override
	public boolean supports(Class<?> clazz) {
		return Item.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		Item item = (Item) target;
		
		if(!StringUtils.hasText(item.getItemName())) {
			errors.rejectValue("itemName","required","필수 입력값 입니다.");
		}
		
		if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
			errors.rejectValue("price", "range", "가격은 1000에서 1000000 사이 여야합니다.");
		}
		if(item.getQuantity() == null || item.getQuantity() < 0) {
			errors.rejectValue("quantity", "max", "양은 음수 여서는 안됩니다.");
		}

	}

}

다음과 같이 itemValidator를 구현 했습니다.

이 이후에는 컨트롤러 단에서 validate메서드를 사용해 유효성을 검사해 주기만 하면 됩니다.

 

 

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

import com.example.demo.Item;
import com.example.demo.validator.ItemValidator;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class ItemController {

	private final ItemValidator itemValidator;
	
	@GetMapping("/itemForm")
	public String itemForm(Model model) {
		model.addAttribute("item",new Item());
		return "itemForm";
	}
	
	
	@PostMapping("/addItem")
	public String addItem(@ModelAttribute Item item,BindingResult bindingResult) {
		itemValidator.validate(item, bindingResult);
		System.out.println(bindingResult.getErrorCount()+"에러의 갯수");
		
		if(bindingResult.hasErrors()) {
			return "itemForm";
		}
		
		return "itemForm";
	}
	
}

validate 메서드를 통해 유효성 검사를 진행하면 bindingResult에는 유효성 검사를 하며 발생한 에러 들을 저장합니다.

이렇게 발생한 에러 정보들을 타임리프에서 받아서 처리해주면 사용자는 무엇이 잘못 요청되었는지를 확인 할 수 있게됩니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	
	<head>
		
	</head>
	<body>
		<form method="post" action="/addItem" th:object="${item}">
		<input placeholder="상품명을 입력하세요" type="text" name = "itemName" />
		<div th:if="${#fields.hasErrors('itemName')}" th:errors="*{itemName}">
			상품명 오류
		</div>
		<input placeholder="가격을 입력하세요" type="text"  name = "price" />
		<div th:if="${#fields.hasErrors('price')}" th:errors="*{price}" >
			상품가격 오류
		</div>
		<input placeholder="상품의 갯수를 입력하세요" type="number" name = "quantity" />
		<div th:if="${#fields.hasErrors('quantity')}" th:errors="*{quantity}">
			양 오류
		</div>
		<input type="submit" value="전송"/>
		</form>
	</body>
	
</html>

th:errors="*{필드명}"을 입력해 주면 해당 필드에 대한 에러 메세지가 출력됩니다.

다음과 같이 일부러 에러를 발생 시켜 보겠습니다.

다음과 같이 에러메세지를 포함한 입력 폼을 보실 수 있습니다.

 

 

앞서 살펴본 컨트롤러에서는 이해하기 쉽도록 직접 validator를 불러와 validate메서드를 사용했지만 이를 여러 경로마다 해주게 되면 불필요한 중복된 코드가 생기게 됩니다. 

이때 사용하는 것이 @InitBinder 와 @Validated 애너테이션 입니다.

 

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.Item;
import com.example.demo.validator.ItemValidator;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class ItemController {

	private final ItemValidator itemValidator;
	
	@InitBinder
	public void init(WebDataBinder dataBinder) {
		dataBinder.addValidators(itemValidator);
	}
	
	@GetMapping("/itemForm")
	public String itemForm(Model model) {
		model.addAttribute("item",new Item());
		return "itemForm";
	}
	
	
	@PostMapping("/addItem")
	public String addItem(@Validated @ModelAttribute Item item,BindingResult bindingResult) {
		System.out.println(bindingResult.getErrorCount()+"에러의 갯수");
		
		if(bindingResult.hasErrors()) {
			return "itemForm";
		}
		
		return "itemForm";
	}
	
}

@InitBinder는 해당 컨트롤러로 들어오는 요청에 대해 추가적인 설정을 할 수 있게 도와줍니다.

위 예제에서는 itemValidator를 추가 해 준 것이죠

이제 @Validated 애너테이션으로 객체의 유효성 검사를 진행 할 수 있습니다.

 

addItem 메서드의 파라미터에 Item객체에 @Validated 애너테이션이 붙은 것을 보실 수 있습니다.

해당 객체를 검사하겠다는 의미 입니다.