ulzcq / validation Goto Github PK
View Code? Open in Web Editor NEW[MVC2] Spring Validation 학습용 리포지토리
[MVC2] Spring Validation 학습용 리포지토리
앞서 우리가 직접 만든 ItemValidator
를 제거하면, 애노테이션 기반 Bean Validation이 정상 동작한다.
LocalValidatorFactoryBean
을 글로벌 Validator로 등록한다.NotNull
같은 애노테이션을 보고 검증을 수행한다.따라서 @Valid
, @Validated
만 적용하면 앞서 배운 검증 로직들이 실행된다.
기본으로 제공되는 오류 메시지를 변경하고 싶으면, error.properties
에 메시지를 등록한다.
특정 필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObejctError)는 직접 자바 코드로 작성하는 것을 권장한다.
등록에는 필요없지만 수정에는 id가 필수이고, 폼에 추가적으로 들어오는 데이터도 있을 수 있다.
그런데 Item 클래스는 공용이므로, 애노테이션으로 등록과 수정의 검증 조건을 동시에 적용할 수는 없다.
해결법 두 가지 중 BeanValidation groups
기능을 알아본다.
오류 발생시 기본적으로 :
class="field-error"
를 붙인다오류메시지가 하나라도 있으면 model에 Map(errors)를 담고 입력폼으로 보냄
StringUtils를 사용해서 if문으로 하나하나 조건을 달고 에러메시지를 담아줌.
th:classappend="${errors?.containsKey('필드명')}?'field-error' : _"
field-error
라는 클래스 정보를 더해서 색상 빨갛게, 에러없으면 _(No-Operation)
으로 아무것도 안함th:if
문을 사용해 errors?.containsKey('globalError')
인 경우에만 오류메시지 출력문제점
타입 오류로 바인딩에 실패하면 스프링은 FieldError
를 생성하면서 사용자가 입력한 값을 넣어두고 오류를 BindingResult
에 담아서 컨트롤러를 호출한다. 타임리프의 th:field
는 오류가 발생하면 FieldError
에서 보관한 값을 사용해 값을 출력
BindingResult
의 FieldError
, ObjectError
사용required.item.itemName
(더 자세히 에러메시지를 작성할 시 이걸 추가하자)required
(덜 중요하면 이걸 재활용하고)resoureces/errors.properties
파일을 생성해 오류메시지를 체계적으로 관리#==ObjectError==
#Level1
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#Level2 - 생략
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}
#==FieldError==
#Level1
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
#Level2 - 생략
#Level3
required.java.lang.String = 필수 문자입니다.
required.java.lang.Integer = 필수 숫자입니다.
min.java.lang.String = {0} 이상의 문자를 입력해주세요.
min.java.lang.Integer = {0} 이상의 숫자를 입력해주세요.
range.java.lang.String = {0} ~ {1} 까지의 문자를 입력해주세요.
range.java.lang.Integer = {0} ~ {1} 까지의 숫자를 입력해주세요.
max.java.lang.String = {0} 까지의 문자를 허용합니다.
max.java.lang.Integer = {0} 까지의 숫자를 허용합니다.
#Level4
required = 필수 값 입니다.
min= {0} 이상이어야 합니다.
range= {0} ~ {1} 범위를 허용합니다.
max= {0} 까지 허용합니다.
#==타입 mismatch== 💛
typeMismatch.java.lang.Integer=숫자를 입력해주세요.
typeMismatch=타입 오류입니다.
#Bean Validation 추가 💛
NotBlank.item.itemName=상품 이름을 적어주세요.
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
# 메시지 설정
spring.messages.basename=errors
#fields
: 오류가 발생하면 FieldError에서 보관한 값을 사용해 값을 출력th:errors
: 오류가 있으면 우선 순위가 가장 높은 오류 메시지 코드를 출력th:errorclass
@Validated
가 컨트롤러 메서드 파라미터에 붙으면 WebDataBinder에 등록한 검증기를 찾아서 실행. 이때 검증기 구분을 위해 supports()가 사용된다.@NotBlank
@NotNull
@Range(min =1000, max = 100000) : 범위 안의 값이어야 함
@Max(9999) : 최대 9999까지만 허용
javax.validation
으로 시작하면 자바 표준,org.hibernate.validator
는 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능(실무에서는 대부분 하이버네이트 validator 사용)
문제 : 등록과 수정의 요구사항은 다를 수 있다. (등록에는 필요없지만 수정에는 id가 필수임) 그런데 엔티티 클래스(예: Item)는 공용이므로, 애노테이션으로 등록과 수정에서의 검증 조건을 동시에 적용할 수는 없다.
해결: groups 기능은 실무에서 거의 쓰지 않고, 폼 객체를 분리하여 사용하는 방식을 주로 이용
예) Item 객체(Entity)의 검증코드(Bean Validation)을 모두 제거하고, ItemSaveForm과 ItemUpdateForm으로 분리하여 각각 등록/수정의 Bean Validation을 설정한다.
그러면 컨트롤러에서는 Form 객체로 전송 받아서 검증을 받고 Item 객체로 변환해서 Repository로 넘긴다.
컨트롤러 코드
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
//빈 객체를 넘긴 이유는 여러가지가 있겠지만, 검증 실패시 이것을 재사용할 수 있기 때문이기도 하다.
return "validation/v4/addForm";
}
@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"; //BindingResult는 자동으로 뷰로 넘어가므로 모델로 안넘겨도 된다
}
//성공 로직
//form -> item 변환. setter 쓰지말고 생성자로 쓰는 것이 더 좋다.
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}";
}
글로벌 에러
1
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>
2
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
필드에러
1 직접 처리(classappend 사용해서 길이 줄임)
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}"
th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
상품명 오류
</div>
</div>
2 th:errorclass, th:errors
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}"
th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
상품명 오류
</div>
</div>
rejectValue()
호출
MessageCodesResolver
를 사용해서 검증 오류 코드로 메시지 코드들을 생성
new FieldError()
를 생성하면서 메시지 코드들을 보관
th:errors
에서 메시지 코드에서 메시지를 순서대로 찾고, 메시지를 노출
app 코드 변경할 필요없이 error.properties 파일만 변경하면 적용되므로 효율적이다.
주로 타입 정보가 맞지 않을 경우 스프링이 직접 검증 오류 코드를 추가한다.
스프링이 자동으로 생성하는 오류코드에 맞춰 error.properties에 원하는 오류 메시지를 추가하면 된다.
typeMismatch.java.lang.타입
typeMismatch
타입오류의 경우 바인딩이 아예 실패해서, 로직 객체에 null이 들어가므로 다른 오류까지 뜨게 된다.
타입 오류 메시지만 뜨게 하려면 추가로 코드 작성이 필요하다.
스프링은 검증을 체계적으로 제공하기 위해 Validator
인터페이스를 제공한다.
Validator
를 상속받는 ItemValidator
클래스를 생성해서 검증 로직을 컨트롤러에서 분리하자.
ItemValidator
를 스프링 빈으로 주입 받는다. → @Component
WebDataBinder
에 Validator(검증기)를 추가하면 해당 컨트롤러에서는 검증기가 자동으로 적용된다.Validated
애노테이션이 메서드 파라미터에 붙으면 WebDataBinder
에 등록한 검증기를 찾아서 실행한다.API 호출 시 JSON으로 데이터를 받아올 때는 HttpMessageConverter
의 @RequestBody
를 사용한다.
API의 Validation의 경우 다음 3가지 경우로 나뉜다.
bindingReuslt.getAllErrors();
는 ObjectError
와 FieldError
를 반환HttpMessageConverter
는 전체 객체 단위로 적용되므로, 메시지 컨버터의 작동이 성공해서 객체를 만들어야 @Valid
, @Validated
가 적용된다.
객체로 변경하지 못하면 예외가 발생하며 컨트롤러도 호출되지 않고 Validator도 적용할 수 없다.
예외 발생 시 원하는 모양으로 예외를 처리하는 방법은 이후 강의에서 다룬다.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.