Bean validation
Bean Validation (Bean 검증)
- 일일히 검증 로직 짜는건 비효율적 →
@NotBlank
,@NotNull
,@Range
등 공통된 검증 사용 가능. - 따라서, annotation으로 깔끔하게 해결.
- build.gradle 에
implementation 'org.springframework.boot:spring-boot-starter-validation'
추가. - Field Error: How to?
- 검증할 클래스 (ex.Item) 필드에 annotation 추가. (
@NotBlank
,@NotNull
,@Range(min= 1000, max= 1000000)
등) - Controller 메소드에
@Validated
추가 → 스프링이 자동으로 global validator 등록, annotation보고 검증 수행. - errors.properties 에 마찬가지로 오류 메세지 바꿀 수 있음.
- 검증할 클래스 (ex.Item) 필드에 annotation 추가. (
# Bean validation
NotBlank= 값을 입력하세요.
Range= {1} ~ {2} 값을 허용합니다.
MAX= 최대 {1} 의 값까지 허용합니다.
- Object Error: How to?
@ScriptAssert
어노테이션 방법이 있긴 하지만, 실무에서는 다른 클래스 필드를 끌어오거나 디비에서 값을 조회해서 가져와야 되는 경우도 있기 때문에 선호되는 방법은 아님. (기능이 너무 단순하고 약함)-
추천하는 방법은 그냥 java 코드로 직접 검증하는 것.
// 복합 룰 검증 if (item.getPrice() != null && item.getQuantity() != null) { int result = item.getPrice() * item.getQuantity(); if (result < 10000) { bindingResult.reject("totalPriceMin", new Object[]{10000, result}, null); } }
- Bean Validation의 한계
- 동일한 객체를 등록할 때와 수정할 때 검증방법이 다를 수 있음 → 공통적으로 해결하지 못함 (충돌이 발생).
- ex) 상품 등록과 수정 시에 요구 사항이 다를 수 있음
- 수정 시 요구사항 - quantity 수량 제한을 풀고, 상품 id도 필수로 필요할 것.
- 해결책 → id: @NotNull 추가, quantity: @Max(9999) 제거? → 해결 X.
- 수정 시 요구사항 - quantity 수량 제한을 풀고, 상품 id도 필수로 필요할 것.
- ex) 상품 등록과 수정 시에 요구 사항이 다를 수 있음
- 동일한 객체를 등록할 때와 수정할 때 검증방법이 다를 수 있음 → 공통적으로 해결하지 못함 (충돌이 발생).
- 해결책 - 크게 2가지
- Bean validation의 groups 기능 사용.
- SaveCheck, UpdateCheck 2개의 Interface class 만들어서 각 필드 어노테이션에 각각 조건을 걸기.
@NotBlank(groups = {SaveCheck.class})
@Range(min= 1000, max= 1000000, groups = {SaveCheck.class, UpdateCheck.class})
- 그리고 Controller에
@Validated(SaveCheck.class)
로 수정. (SaveCheck 조건이 걸린 어노테이션만 골라 적용) - 그러나, 실무에서는 많이 쓰지 않음.
- Why? 생각해보면 real world 에서는 등록과 수정의 구체적인 내용이 상당히 많이 다른 경우가 많고, 따라서 이런 경우엔 두 개의 객체로 각각 분리해서 받는게 훨씬 나음.
- SaveCheck, UpdateCheck 2개의 Interface class 만들어서 각 필드 어노테이션에 각각 조건을 걸기.
- 폼 전송 객체 분리 (ex. 등록용 폼 객체, 수정용 폼 객체)
- 상품 등록, 수정에 각각의 필요에 맞게 따로 분리해서 객체를 생성
- 폼 데이터 전달을 위한 별도의 객체 사용
- HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository
- 컨트롤러에서 Item 객체를 생성하는 변환 과정이 추가된다는 번거로움이 있지만, 실무에서의 복잡한 상황에 대응하기 위해 가장 선호되는 방법.
- 폼 데이터 전달을 위한 별도의 객체 사용
@Data public class ItemSaveForm { @NotBlank private String itemName; @NotNull @Range(min= 1000, max= 1000000) private Integer price; @NotNull @Max(9999) private Integer quantity; }
@Data public class ItemUpdateForm { @NotNull private Long id; @NotBlank private String itemName; @NotNull @Range(min= 1000, max= 1000000) private Integer price; // 수정 시 수량은 제한 없음 @NotNull private Integer quantity; }
- 상품 등록, 수정에 각각의 필요에 맞게 따로 분리해서 객체를 생성
- Bean validation의 groups 기능 사용.
- Api 호출 시 검증
@RestController
를 이용한 Json Api 통신 시에도@Validated
검증을 사용할 수 있음.- 차이점
@ModelAttribute
대신,@RequestBody
를 이용한다는 점이 차이.- 다만,
@ModelAttribute
는 각각의 필드에 세밀하게 작용해 타입 오류가 발생해도 bindingResult에 넘기고 Controller호출은 가능했지만, @RequestBody
를 이용한 Api 호출 시에는 타입 오류가 발생해 JSON 객체로 변경되지 못하면 아예 다음 단계 진행이 불가능. 즉, Controller 호출과 검증 모두 실행되지 않는다.
- (복습)
@ModelAttribute
,@RequestParam
,@RequestBody
@ModelAttribute
- HTTP 요청 파라미터를 쉽게 받아주는 기능. 객체를 자동으로 생성해주고, 넘어온 파라미터를 객체 필드에 set까지 해줘 바로 사용할 수 있게 함.@RequestParam
- 마찬가지로 HTTP 요청 파라미터를 쉽게 받아주는 기능. 이 경우는 객체가 아니라 String, int 와 같은 단순한 타입의 파라미터를 바인딩 해주어 사용할 수 있게 함.@RequestBody
- HTTP 메세지의 Body 정보를 직접 조회하는 기능. Api 통신 시 Json형식으로 정보를 받고 넘길 수 있는데 이 경우에 사용함.