ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링MVC - 2편] 검증 (4)
    스프링&스프링부트 2025. 3. 2. 16:03

    Form 전송 객체 분리 

    실무에서는 groups 사용하지 않는데 등록시 폼에서 전달하는 데이터가 Item 도메인 객체와 맞지 않기 때문이다

    그래서 보통 Item 직접 전달받는 것이 아니라, 복잡한 폼의 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다

    •  데이터 전달에 Item 도메인 객체 사용
      • HTML Form -> Item -> Controller -> Item -> Repository
      • 장점: Item 도메인 객체를 컨트롤러리포지토리 까지 직접 전달해서 중간에 Item 만드는 과정이 없어서 간단하다
      • 단점: 간단한 경우에만 적용할  있다 수정시 검증이 중복될  있고, groups를 사용해야 한다
    •  데이터 전달을 위한 별도의 객체 사용
      • HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository
      • 장점: 전송하는  데이터가 복잡해도 거기에 맞춘 별도의  객체를 사용해서 데이터를 전달 받을  있다
      • 단점:  데이터를 기반으로 컨트롤러에서 Item 객체를 생성하는 변환 과정이 추가된다

     

     

    ITEM

    @Data
    public class Item {
    	private Long id;
    	private String itemName;
    	private Integer price;
    	private Integer quantity;
    }

     

     

    ItemSaveForm - ITEM 저장용

    @Data
    public class ItemSaveForm {
    	@NotBlank
    	private String itemName;
    
    	@NotNull
    	@Range(min = 1000, max = 1000000)
    	private Integer price;
    
    	@NotNull
    	@Max(value = 9999)
    	private Integer quantity;
    }

     

     

     

    **ItemUpdateForm - ITEM 수정용 **

    @Data
    public class ItemUpdateForm {
    	@NotNull
    	private Long id;
    
    	@NotBlank
    	private String itemName;
    
    	@NotNull
    	@Range(min = 1000, max = 1000000)
    	private Integer price;
        
    	// 수정에서는 수량은 자유롭게 변경할 수 있다.
    	private Integer quantity;
    }

     

     

    ValidationItemControllerV4 수정

    @Slf4j
    @Controller
    @RequestMapping("/validation/v4/items")
    @RequiredArgsConstructor
    public class ValidationItemControllerV4 {
    	...
        
        @PostMapping("/add")
    	public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    		if (form.getPrice() != null && form.getQuantity() != null) {
    			int resultPrice = form.getPrice() * form.getQuantity();
    			if (resultPrice < 10000) {
    				bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
    			}
    		}
            
    		if (bindingResult.hasErrors()) {
    			log.info("errors={}", bindingResult);
    			return "validation/v4/addForm";
    		}
            
    		// 성공 로직
    		Item item = new Item();
    		item.setItemName(form.getItemName());
    		item.setPrice(form.getPrice());
    		item.setQuantity(form.getQuantity());
    
    		Item savedItem = itemRepository.save(item);
    		redirectAttributes.addAttribute("itemId", savedItem.getId());
    		redirectAttributes.addAttribute("status", true);
    		return "redirect:/validation/v4/items/{itemId}";
    	}
        
        ...
        
    	@PostMapping("/{itemId}/edit")
    	public String edit(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {
    		if (form.getPrice() != null && form.getQuantity() != null) {
    			int resultPrice = form.getPrice() * form.getQuantity();
    			if (resultPrice < 10000) {
    				bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
    			}
    		}
            
    		if (bindingResult.hasErrors()) {
    			log.info("errors={}", bindingResult);
    			return "validation/v4/editForm";
    		}
            
    		Item itemParam = new Item();
    		itemParam.setItemName(form.getItemName());
    		itemParam.setPrice(form.getPrice());
    		itemParam.setQuantity(form.getQuantity());
    		
            itemRepository.update(itemId, itemParam);
    		return "redirect:/validation/v4/items/{itemId}";
    	}
        ...
    }

     

     

    Bean Validation - HTTP 메시지 컨버터

      • @ModelAttribute HTTP 요청 파라미터(URL 쿼리 스트링, POST Form) 다룰 사용한다
      • @RequestBody HTTP Body 데이터를 객체로 변환할 사용한다. 주로 API JSON 요청을 다룰 사용한다

    ValidationItemApiController 생성

    @Slf4j
    @RestController
    @RequestMapping("/validation/api/items")
    public class ValidationItemApiController {
    	@PostMapping("/add")
    	public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {
    		log.info("API 컨트롤러 호출");
    		if (bindingResult.hasErrors()) {
    			log.info("검증 오류 발생 errors={}", bindingResult);
    			return bindingResult.getAllErrors();
    		}
    		log.info("성공 로직 실행");
    		return form;
    	}
    }

    API 경우 3가지 경우를 나누어 생각

    • 성공 요청: 성공

    • 실패 요청: JSON 객체로 생성하는 자체가 실패함

    • 검증 오류 요청: JSON 객체로 생성하는 것은 성공했고, 검증에서 실패함

     

     

    @ModelAttribute vs @RequestBody
    • @ModelAttribute 필드 단위로 정교하게 바인딩이 적용된다 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator 사용한 검증도 적용할 있다
    • @RequestBody HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다 컨트롤러도 호출되지 않고 Validator도 적용할 수 없다

     

     

     

     

    728x90
Designed by Tistory.