SPRING/Spring MVC

[SpringMVC] 검증(Validation)(8) - Bean Validation ( 폼객체 )

IT록흐 2023. 8. 30. 15:38
반응형

 

 

[SpringMVC] 검증(Validation)(7) - Bean Validation ( groups )

[SpringMVC] 검증(Validation)(5) - Bean Validation [SpringMVC] 검증(Validation)(4) - BindingResult ( @Validated ) [SpringMVC] 검증(Validation)(3) - BindingResult ( rejectValue 메소드 ) [SpringMVC] 요청데이터 검증(Validation)하기(2) - Bindi

lordofkangs.tistory.com

 

 

지난 포스팅에서 하나의 객체에 서로 다른 유효성 검사가 필요한 경우를 다루어 보았다. 

 

Bean Validation은 groups 설정을 제공하여, 하나의 클래스에 서로 다른 유효성 검사가 적용될 수 있도록 기능을 제공하지만 코드가 지저분해지고 그룹을 나누기 위한 의미없는 인터페이스를 생성해야 했다. 실무에서는 groups 보다는 폼객체를 생성하여 문제를 해결한다.

 

 

폼객체( Form Object )

 

폼객체는 DTO라 부르기도 한다. 특정 요청(Request)에 특화된 클래스를 만드는 것이다.

 

 

하나의 클래스로 요청에 따라 여러가지 폼클래스로 분리하고, 요청이 들어오면 요청에 맞는 폼객체를 생성하고 유효성 검사를 하는 원리이다. 저장 요청이면 저장 Form을, 수정 요청이면 수정 Form을 만들어 유효성 검사를 한다. 이를 코드로 확인해보자. 

 

 

Item 클래스 ( groups 사용 )

@Data
public class Item {

    @NotNull(groups = UpdateCheck.class)
    private Long id;

    @NotBlank(groups = {SaveCheck.class,UpdateCheck.class})
    private String itemName;

    @NotNull(groups = {SaveCheck.class,UpdateCheck.class})
    @Range(min=1000,max=1000000,groups = {SaveCheck.class,UpdateCheck.class})
    private Integer price;

    @NotNull(groups = {SaveCheck.class,UpdateCheck.class})
    @Max(value=9999,groups = SaveCheck.class)
    private Integer quantity;

}

 

 

위 클래스는 groups 설정을 한 Item 클래스이다.  이를 용도에 따라 Form 클래스 분리해보자. 

 

 

ItemSaveForm 클래스

@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 클래스

@Data
public class ItemUpdateForm {

    @NotNull
    private Long id;

    @NotBlank
    private String itemName;

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

    private Integer quantity;
}

 

위와 같이, Item 클래스를 용도에 따라 분리하면 groups 설정이 필요 없어진다. groups 설정이 사라지니 코드도 단순해졌다. 그럼 Controller 코드를 보자.

 

Controller ( groups 사용 )

// 저장 메소드
@PostMapping("/add")
public String addItem(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {
 
	// 중략 ...
 
}
 
// 수정 메소드
@PostMapping("/edit")
public String edit(@PathVariable Long itemId, @Validated(UdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {
 
	// 중략 ...
    
}

 

groups 설정을 사용하면 @Validated에 group을 나타내는 인터페이스명을 설정해주어야 했다. 그리고 Item 클래스를 파라미터로 사용한다. 그럼 폼객체를 사용해보자. 

 

Controller ( 폼객체 사용 )

//저장 메소드
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult ) {
    
    //중략 ...
    
}

//수정 메소드
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {

	//중략 ...
}

 

Item 클래스가 ItemSaveForm 클래스, ItemUpdateForm 클래스로 분리되었다. 저장시 필요한 유효성검사는 ItemSaveForm에서 수정시 필요한 유효성 검사는 ItemUpdateForm 객체에서 진행하면 된다. 이는 실무에서 가장 많이 사용되는 방식이다. 

 

 

@RequestBody와 @Validate 

 

지금까지 다룬 내용은 @ModelAttribute가 선언된 경우이다. @ModelAttribute가 선언되어 있으면 Spring은 GET 방식이나 POST 방식으로 들어오는 요청데이터를 JAVA객체와 바인딩한다.  이 과정은 필드와 데이터가 직접 바인딩되므로, 바인딩 과정에서 에러가 발생해도 Spring이 Exception을 핸들링 할 수 있다. 그래서 특정 필드의 바인딩 과정에서 에러가 발생해도 다른 필드의 바인딩은 정상적으로 이루어지고 유효성검사까지 진행된다. 

 

그러나 

 

@RequestBody가 선언된 파라미터는 다르다. 이는 HTTP Body에서 직접 데이터를 가져 온 다음, 컨버터가 동작하여 객체가 생성된다. Spring이 필드 하나하나에 관여할 수 없다는 의미다. 만약 필드와 데이터 바인딩 과정 중 에러가 발생하면 바로 Exception 처리가 된다. 그러므로 @RequestBody가 선언된 파라미터에 @Validated를 선언하면 바인딩 에러가 발생하지 않은 경우 유효성 검사가 정상 동작하지만 바인딩 에러가 발생하면 유효성 검사를 진행하지 못한다. 

 

그러므로 HTTP Body에서 직접 데이터를 가져온 경우, 바인딩 과정에서 발생하는 Exception을 핸들링해야 한다. 이와 관련해서는 나중에 포스팅으로 정리해보겠다. 

 

 


 

 

이것으로 8개의 포스팅에 걸쳐 진행된 Validation(검증)에 대한 정리가 끝났다. 

 

하나씩 정리하면, 

 

Request(요청)으로 들어온 데이터는 Controller에서 검증되어야 한다. 유효성 검사는 Controller 내부에서 검증하면 되지만 바인딩 과정에서 발생하는 에러는 Exception을 일으킨다. Spring은 Controller 외부에서 발생하는 에러를 처리하고 에러내용을 BindingResult 객체에 담는다. 이로써 Exception이 발생하지 않고 Controller 로직이 실행된다. Controller에서 유효성 검사를 진행하고 에러가 있다면 BindingResult에 담는다. 그러나 Controller는 비즈니스 로직을 처리하는 곳으로 검증 로직은 따로 분리하는 것이 좋다. 검증이 필요한 파라미터에 @Validated(or @Valid)를 선언하여 검증기가 돌 수 있도록 한다. 검증기는 Validator 인터페이스 구현체를 구현하여 Bean으로 등록할 수 있지만, 대부분의 검증기는 비슷한 로직을 가지고 있으므로 Bean Validation을 주로 사용한다. 

 

Bean Validation은 JAVA에서 제공하는 어노테이션 기반 데이터 검증 방식으로, 주로 객체 안 필드에 주입된 데이터의 유효성 검사에 사용된다. 그러나 한 가지 문제가 있다. 하나의 클래스에 다양한 유효성 검사를 담지 못한다는 것이다. groups 설정을 사용하면 가능하지만 이는 코드가 복잡해지므로 추천하지 않는다. 하나의 클래스에 다양한 유효성 검사를 담지 못하면 다양한 클래스를 만들어주면 된다. 특정 유효성 검사에 특화된 Form 객체를 만들면 다양한 유효성검사를 적용할 수 있게 된다. 

 

 

 


 

 

참고자료

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

 

 

 

반응형