지난 포스팅에 이어, 이번 포스팅에서는 5) ~ 8)
까지의 내용을 정리한다.
👉 목차는 다음과 같다.
1) 검증 요구사항
2) 프로젝트 설정 V1
3) 검증 직접 처리 - 소개
4) 검증 직접 처리 - 개발
5) 프로젝트 준비 V2
6) BindingResult1
7) BindingResult2
8) FieldError, ObjectError
9) 오류 코드와 메시지 처리1
10) 오류 코드와 메시지 처리2
11) 오류 코드와 메시지 처리3
12) 오류 코드와 메시지 처리4
13) 오류 코드와 메시지 처리5
14) 오류 코드와 메시지 처리6
15) Validator 분리1
16) Validator 분리2
17) 정리
이제 다음 단계로 넘어가기 위해서 V2 프로젝트를 준비하자.
(추후에 학습하기 용이하도록) 앞서 만든 V1 버전의 기능을 유지하기 위해, 컨트롤러와 템플릿 파일을 복사하자.
ValidationItemControllerV1
을 복사하여 ValidationItemControllerV2
로 붙여넣자.ValidationItemControllerV2
클래스 내부에 v1
으로 되어있는 URL 경로를 v2
로 수정하자. ( validation/v1/
-> validation/v2/
)v1
디렉토리를 복사하여 v2
로 붙여넣자.v2
폴더 아래 4개 파일 (addForm.html
, editForm.html
, item.html
, items.html
) 내부에 모두 URL 경로를 변경하자. ( validation/v1/
-> validation/v2/
)v2
버전으로 잘 접근됨을 확인할 수 있다.이제 다음 내용부터 스프링이 제공하는 검증 오류 처리 매커니즘에 대해서 알아보자.
지금부터 스프링이 제공하는 검증 오류 처리 방법을 알아보자.
여기서 핵심은 BindingResult 이다.
👉 코드로 바로 확인해보자.
ValidationItemControllerV2 - addItemV1: 다음과 같이 수정하자.
Item item
에 바인딩이 된 결과가 bindingReult
에 담긴다. (따라서 이전 V1 버전에서 errors
의 역할을 bindingReult
가 하므로, errors
는 이제 없어도 된다.)BindingResult bindingResult
파라미터의 위치는 @ModelAttribute Item item
바로 다음에 와야 한다.addItem
였으나, addItemV1
으로 수정하였다.FieldError
객체를 생성해서 bindingResult
에 담아두면 된다.public FieldError(String objectName, String field, String defaultMessage) {}
objectName
: @ModelAttribute
이름field
: 오류가 발생한 필드 이름defaultMessage
: 오류 기본 메시지ObjectError
객체를 생성해서 bindingResult
에 담아두면 된다.public ObjectError(String objectName, String defaultMessage) {}
objectName
: @ModelAttribute 의 이름defaultMessage
: 오류 기본 메시지bindingResult
에 오류가 존재하면(bindingResult.hasError()) 입력 폼으로 이동한다.model
에 검증 오류 정보를 담는 로직은 생략해도 된다. bindingResult
는 자동으로 뷰에 전달된다. (따라서 model
은 이제 필요하지 않으므로, 컨트롤러 파라미터에서도 제거하였다.)
👉 이제 화면을 처리해보자.
v2/addForm.html: 다음과 같이 수정하자.
globalErros
는 컬렉션으로, each
를 사용해서 반복을 적용한다. )✔️ 타임리프 스프링 검증 오류 통합 기능
BindingResult
를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.#fields
: #fields
로 BindingResult
가 제공하는 검증 오류에 접근할 수 있다.th:errors
: 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if
의 편의 버전이다.th:errorclass
: th:field
에서 지정한 필드에 오류가 있으면 class
정보를 추가한다.✔️ 참고
이번에는 BindingResult
에 대해서 조금 더 자세히 알아보자.
BindingResult
BindingResult
가 있으면 @ModelAttribute
에 데이터 바인딩시 오류가 발생해도 컨트롤러가 호출된다.BindingResult 가 없으면
: 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.BindingResult 가 있으면
: 오류 정보( FieldError
)를 BindingResult
에 담아서 컨트롤러를 정상 호출한다.BindingResult
가 있으면 그 문제에 대한 결과를 BindingResult
에 담아준다. 담아주고 컨트롤러를 정상 호출한다. 따라서 로그와 화면에서 그 오류 정보가 노출되는 것이다. (관련 메시지 정보는 이후에 뒤에서 설명한다.)
BindingResult에 검증 오류를 적용하는 3가지 방법
ModelAttribute
의 객체에 타입 오류 등으로 바인딩이 실패하는 경우, 스프링이 FieldError
생성해서 BindingResult
에 넣어준다.bindingResult.addError(...)
)Validator
를 사용 -> 이것은 뒤에서 설명주의
BindingResult
는 검증할 대상 바로 다음에 와야한다. 순서가 중요하다. (예를 들어서, @ModelAttribute Item item
, 바로 다음에 BindingResult
가 와야 한다.)BindingResult
는 Model
에 자동으로 포함된다.BindingResult와 Errors
org.springframework.validation.Errors
org.springframework.validation.BindingResult
BindingResult
는 인터페이스이고, Errors
인터페이스를 상속받고 있다.BeanPropertyBindingResult
라는 것인데, 둘다 구현하고 있으므로 BindingResult
대신에 Errors
를 사용해도 된다. Errors
인터페이스는 단순한 오류 저장과 조회 기능을 제공한다. BindingResult
는 여기에 더해서 추가적인 기능들을 제공한다. addError()
도 BindingResult
가 제공하므로 여기서는 BindingResult
를 사용하자. 주로 관례상 BindingResult
를 많이 사용한다.
✔️ 정리
BindingResult
, FieldError
, ObjectError
를 사용해서 오류 메시지를 처리하는 방법을 알아보았다.FieldError
와 ObjectError
에 대해 좀 더 자세히 알아보자.목표
FieldError
, ObjectError
에 대해서 더 자세히 알아보자.
👉 코드로 확인해보자.
FieldError
와 ObjectError
생성자 파라미터가 몇개 추가되었다. 자세히 알아보자.
FieldError 생성자
objectName
: 오류가 발생한 객체 이름field
: 오류 필드 (필드명)rejectedValue
: 사용자가 입력한 값 (거절된 값)bindingFailure
: 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값. (데이터 자체가 넘어오는 것에 실패했는지를 묻는 것. 위 예시같은 경우, 데이터 자체는 잘 들어왔고 이후 비즈니스 검증상 요구사항에 맞지않는 것이기 때문에 false로 지정한다.)codes
: 메시지 코드arguments
: 메시지에서 사용하는 인자defaultMessage
: 기본 오류 메시지ObjectError 생성자
오류 발생시 사용자 입력 값 유지
@ModelAttribute
에 바인딩되는 시점에 오류가 발생하면, 모델 객체에서 사용자 입력 값을 유지하기 어렵다. 예를 들어서 가격에 숫자가 아닌 문자가 입력된다면 가격은 Integer
타입이므로 문자를 보관할 수 있는 방법이 없다. 그래서 오류가 발생한 경우 사용자 입력 값을 보관하는 별도의 방법이 필요하다. 그리고 이렇게 보관한 사용자 입력 값을 검증 오류 발생시 화면에 다시 출력하면 된다.FieldError
는 오류 발생시 사용자 입력 값을 저장하는 기능을 제공한다.rejectedValue
가 바로 오류 발생시 사용자 입력 값을 저장하는 필드다. bindingFailure
는 타입 오류 같은 바인딩이 실패했는지 여부를 적어주면 된다. 여기서는 바인딩이 실패한 것은 아니기 때문에 false
를 사용한다.addItemV1
메서드에서, 검증 로직에서 실패한 경우에 대한 입력값은 화면에서 유지되지 않았지만, 상품 가격에 "qqqq"를 입력한 경우에는 입력값이 유지되었었다. (타입 오류)타임리프의 사용자 입력 값 유지
th:field="*{price}"
th:field
는 매우 똑똑하게 동작하는데, 정상 상황에는 모델 객체의 값을 사용하지만, 오류가 발생하면 FieldError
에서 보관한 값을 사용해서 값을 출력한다.스프링의 바인딩 오류 처리
FieldError
를 생성하면서 사용자가 입력한 값(qqqq)을 넣어둔다. 그리고 해당 오류를 BindingResult
에 담아서 컨트롤러를 호출한다. 따라서 타입 오류 같은 바인딩 실패시에도 사용자의 오류 메시지를 정상 출력할 수 있다.강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.