개발자로 후회없는 삶 살기
[문법] 검증 본문
서론
※ 과거에 기록한 내용에서 중요한 부분만 발췌하여 모두가 이해하기 쉽게 다시 서술한다.
본론
- 검증 요구사항
타입 검증 : 가격, 수량엔 숫자만 가능
필드 검증 : null 불가, 가격에 범위 검증, 수량 검증
지금까지 만든 프로그램은 상품명을 입력 안해도 되고, 가격, 수량에 문자를 적으도 된다. 웹 서비스는 이러한 것을 모두 검증으로 막아야 한다.
검증 실패시 중요한 것은 고객의 편의성이다. 폼에 고객이 넣은 데이터 그대로 다시 보여줘야 한다. 기존 정보와 error 정보를 담아서 폼에 넣어야 한다.
- 검증 하기
컨트롤러에 검증 로직을 작성한다. 먼저 오류가 뜨면, 어떤 오류가 떴나 담는 객체가 필요하다. 검증 오류를 보관하는 Map을 만든다.
필드 별로 실패 정보를 담는다.
-> 검증에 실패
오류가 하나라도 있으면 모델에 errors 정보를 담고 다시 입력 폼 뷰로 보낸다. 실패 시 return으로 밑에 성공 로직을 타지 않는다.
실행해보면,.
errors를 출력해보면 검증에 실패한 정보가 그대로 담겨있다.
또한, 기존 정보는 잘 남아있는데, 그 이유는 @ModelAttribute를 하면 Model 객체에 자동으로 담아준다. Model을 파라미터에서 없애도 자동으로 모델에 들어간다.
- BindingResult
스프링이 위에 작성한 기능을 제공한다. BindingResult에 Item에 바인딩된 결과가 담긴다. BindingResult가 errors의 역할을 한다.
파라미터 : ObjName(ModelAttribute에 담기는 이름), 오류가 발생한 field, 메세지
필드 단위 에러는 addError(fieldError) 로 처리한다. BindingResult가 Errors를 대신하는 오류와 메세지를 담는 애이다. 글로벌 오류는 ObjError이다. erros는 map에 필드명, 에러 메세지만 가지고 있었는데 바인딩은 ObjName을 파라미터로 가지고 있는 것 외에 차이가 없다.
→ 검증 실패 시
검증 실패 시 바인딩은 자동으로 뷰에 담겨서 넘어간다.
에러를 발생시키면 필드 에러가 났다고 알려준다.
바인딩은 스프링이 제공하는 검증 오류 보관소로 @ModelAttri나 @RequestBody 클래스 바인딩 시 오류가 발생해도 컨트롤러가 호출된다.
→ ModelAttribute 바인딩 시 타입 오류 발생
가격을 입력할 때 문자를 넣으면 오류가 발생한다. 바인딩을 제거하고 실행하면 404 오류 페이지로 가버린다. 바인딩이 있을 땐 일단 컨트롤러가 호출된다. 어떤 게 문제인지 무조건 바인딩에 담기고 본다.
바인딩이 없으면 : 404 에러 페이지 렌더링되고 컨트롤러가 호출되지 않음
있으면 : 오류가 있어서 바인딩이 안되고 컨트롤러 호출 (특정 필드 오류로 new FieldError로 스프링이 만듦)
스프링이 ModelAttri로 값을 바인딩하고 있는데 바인딩 시 문제가 발생하면 바인딩에 문제를 담고 컨트롤러가 정상 호출된다. 바인딩이 없으면 타입 에러로 오류 페이지를 띄어버린다.
→ 바인딩 검증 오류 3가지
1) 객체의 타입 오류로 스프링이 자동으로 만든 FieldError
2) 개발자가 직접 new FieldError로 검증
3) Validator 분리
-> 기존 값 유지 방법
검증 실패시 form에 기존 값을 유지해야, 사용자들이 편리하게 서비스를 이용할 수 있다. rejectvalue를 주면 검증에 실패한 기존 정보가 입력된다. 타입 에러가 발생한 것이 아니기에 Failure는 false가 들어간다.
타입 오류 : 스프링이 자동으로 넣어주는 오류러 Failure true
비즈니스 검증 오류 : 개발자가 만든 비즈니스 오류로 Failure false
타입 에러가 아닌 경우 타입이 맞으면 오류가 발생하더라도 바인딩이 되어 값을 가지고 있다.(price라면 1억을 가지고 있다.) 하지만 타입이 안 맞으면 price는 null이다. price 값이 null이니 거절된 값인 reject가 들어간다.
추후 바인딩에 저장된 오류 정보를 이용해서 예외 핸들러에서 검증을 처리한다.
- 코드 간소화
사실 BindingResult는 검증에 실패한 ObjName을 target 정보로 알고 있고, 이를 간소화 할 수 있다.
→ rejectvalue(), reject()
rejectvalue : 필드 에러
reject : 글로벌 에러
바인딩이 제공하는 메서드를 사용하면 깔끔하게 검증 오류를 다룰 수 있다.
target을 알고 있기에 ObjName이 사라지고, 필드명과 메세지만 잘 넣으면 된다. rejectvalue와 failure는 자동으로 내장하고 있다.
global 에러는 필드명도 없으니 메세지와 파라미터만 넣어주면 된다.
- Validator 분리
검증 로직이 컨트롤러에 너무 많다. 컨트롤러는 호출만 하고 검증 로직은 따로 모아두는 것이 좋다.
Validator 인터페이스를 구현한다.
빈 검증기는 모두 이러한 검증 인터페이스를 구현하고 빈으로 등록한 구현체이다.
→ 컨트롤러 구현
컨트롤러에는 이제 검증기를 호출하는 메서드를 만들면 된다. 검증기를 빈 등록하고 컨트롤러에서 주입한다.
스프링의 validator 인터페이스를 구현하면 원하는 컨트롤러의 매핑 메서드가 호출될 때마다 항상 검증기가 적용될 수 있도록 할 수 있다. @Validated를 넣으면 item에 대해서 자동으로 검증기가 수행되고 바인딩에 결과가 담긴다. WebDataBinder에 등록한 검증기를 찾아서 실행한다.
- 빈 검증
검증 코드를 직접 if문으로 짜는 게 매우 번거롭다. 근데 사실 특정 필드에 대한 검증 로직은 대부분 null, 범위 등 매우 일반적인 로직이다. 그래서 빈 검증이 나왔다.
이를 빈 검증이라고 하고, 이렇게 하면 if문 로직을 다 지울 수 있다.
→ 사용법
gradle에 검증기를 불러오면 글로벌 검증기를 사용할 수 있다. validated가 등록된 검증기를 인지해서 @기반 검증을 할 수 있다. validated로 검증기를 부르고 바인딩에 검증 결과가 담겨서 뷰로 넘어간다. (Rest API를 사용하면 바인딩에 검증 결과가 담기고 예외 핸들러에서 처리한다.)
→ 검증 순서
ModelAttribute로 각 필드에 값을 넣고, 성공하면 다음 필드를 보고 실패하면 FieldError를 추가한다. 빈 검증은 바인딩에 성공한 필드만 검증한다.
타입 체크 > NotNull, Range 등 빈 검증
타입 오류로 값이 들어오지 않은 필드는 검증하지 않고 타입이 맞아서 바인딩 성공 후 검증을 하여 범위 등 검증을 실패하면 fieldError나 ObjError를 추가한다.
숫자에 문자를 넣으면 typemismatch 오류 메세지만 뜬다. 빈 검증은 되지 않고 타입 검증만 된다. ModelAttribute는 바인딩에 실패해도 컨트롤러가 호출된다.
- 빈 검증 에러코드, 에러 메세지
빈 검증이 기본적으로 제공하는 오류 메세지를 바꾸려면
빈 검증도 MessageCodeResolver 포멧을 사용하기 때문에 error properties 양식을 고치면 된다.
{0} : 필드명
{1, 2} : Range의 min, max 값
메세지 파일에서 메세지를 못 찾으면, message 속성에서 찾고, 여기 없으면 기본 값을 쓴다.
🚨 오브젝트 오류
도메인과 관련된 비즈니스 로직 오류는 도메인과 서비스에 if문으로 직접 작성하는 것이 좋다.
- 폼 전송 객체 분리
실무에서 로그인을 할 때 member 필드만 가지고 하는 경우는 거의 없다. 약관 등 다양한 데이터가 들어온다. 따라서 목적에 맞게 dto를 분리해서 구현하는 것이 좋다.
- Http API 방식
빈 검증은 API 방식에도 적용된다.
Request Body에서 json을 객체로 바인딩할 때, 빈 검증이 적용된다. 숫자에 문자를 입력하면 Request Body 방식은 json을객체로 바꾸고 빈 검증을 하고 컨트롤러를 호출하는데 타입 에러로 객체를 만드는 것에 실패하여 빈 검증을 못하고 컨트롤러 호출도 못 한다.
→ API 방식 경우의 수
1) 성공 요청 : 성공
2) 타입 에러 요청 : 타입 에러 발생, 객체 생성 실패 후 빈 검증 수행 X
3) 타입 에러 X, 빈 검증 실패 요청 : 객체 생성은 성공하고 빈 검증 에러로 Field Error 등록
3번을 해보면 json을 객체로 만드는 건 성공했고 오류가 발생해서 바인딩에 오류가 추가된다.
-> 정리
1) ModelAttribute : 타입 에러 발생 시 빈 검증은 안되지만 컨트롤러는 호출
2) RequestBody : 타입 에러 발생 시 빈 검증도 안되고 컨트롤러 호출도 안 됨
'[백엔드] > [spring | 학습기록]' 카테고리의 다른 글
[문법] 스프링 예외처리 (0) | 2024.08.05 |
---|---|
[문법] 필터, 인터셉터 (0) | 2024.08.04 |
[문법] 요청과 응답, HttpMessageConverter (0) | 2024.08.03 |
[문법] Spring 핸들러 어댑터, API 설계 실전 (0) | 2024.08.02 |
[문법] 웹 서버와 서블릿 (0) | 2024.08.01 |