BackEnd/spring

Bean Validation

연향동큰손 2024. 8. 1. 21:40

기존의 프로젝트에 Bean Validation을 적용해보았다.

 

<Item>

package hello.itemservice.domain.item;

import lombok.Data;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class Item {

    private Long id;

    @NotBlank(message = "공백X")
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    @NotNull
    @Range(max = 9999)
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

 

<ValidationItemController>

package hello.itemservice.web.validation;

import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

@Slf4j
@Controller
@RequestMapping("validation/v3/items")
@RequiredArgsConstructor
public class ValidationItemControllerV3 {

    private final ItemRepository itemRepository;

    @GetMapping
    public String items(Model model) {
        List<Item> items = itemRepository.findAll();
        model.addAttribute("items", items);
        return "validation/v3/items";
    }

    @GetMapping("/{itemId}")
    public String item(@PathVariable long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "validation/v3/item";
    }

    @GetMapping("/add")
    public String addForm(Model model) {
        model.addAttribute("item", new Item());
        return "validation/v3/addForm";
    }


    @PostMapping("/add")
    public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v3/addForm";
        }
        //성공 로직
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v3/items/{itemId}";
    }

    @GetMapping("/{itemId}/edit")
    public String editForm(@PathVariable Long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "validation/v3/editForm";
    }

    @PostMapping("/{itemId}/edit")
    public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
        itemRepository.update(itemId, item);
        return "redirect:/validation/v3/items/{itemId}";
    }

}

 

 

addItem에 @Validated가 붙으면서 자동으로 검증대상에 추가가 되고  Item 필드값으로 잘못된 정보를 입력하면 다음과 같은 과정으로 검증이 실행된다.

 

1. @ModelAttribute 각각의 필드에 타입변환 시도

     1) 성공하면 다음으로

     2) 실패하면 typeMismatch로 FieldError 추가

2. Validator 적용

 

여기서 알아둬야 할 점은 바인딩에 성공한 필드만 Bean Validation이 적용된다.

만약 타입변환에 실패하면 typeMismatch FieldError 추가되고 Bean Validation에는 적용되지 않는다.

 


 

에러 코드 등록

 

NotBlank라는 오류 코드를 기반으로 errors.properties에 오류 메시지를 입력할 수 있다.

NotBlank.item.itemName=상품 이름을 적어주세요.

#Bean Validation 추가
NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

 

 

 

<실행 결과>

 

 


오브젝트 오류 처리

 

오브젝트 오류를 처리하기 위해서는 @ScriptAssert()를 사용하면 된다.

 

<Item>

package hello.itemservice.domain.item;

import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
@ScriptAssert(lang="javascript", script = "_this.price * _this.quantity>=10000", message = "총합이 10000원 넘게 해주세요") //오브젝트 오류를 처리하기 위해 추가
public class Item {

    private Long id;

    @NotBlank(message = "공백X")
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    @NotNull
    @Range(max = 9999)
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

 

<실행결과>

 

이 방법이 편리하긴 하지만, 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다고 한다.

따라서 오브젝트 오류는 자바 코드로  다루는것을 권장한다.

 

//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
    int resultPrice = item.getPrice() * item.getQuantity();
    if (resultPrice < 10000) {
        bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
    }
}

 

'BackEnd > spring' 카테고리의 다른 글

회원가입 로그인 구현  (0) 2024.08.19
BeanValidation-Form 전송 객체 분리  (0) 2024.08.05
Validator 분리  (1) 2024.07.31
검증 - 개선  (1) 2024.07.26
검증 - BindingResult  (0) 2024.07.26