개발자로 후회없는 삶 살기

[문법] 검증 본문

[백엔드]/[spring | 학습기록]

[문법] 검증

몽이장쥰 2024. 8. 4. 13:52

서론

※ 과거에 기록한 내용에서 중요한 부분만 발췌하여 모두가 이해하기 쉽게 다시 서술한다.

 

본론

- 검증 요구사항

타입 검증 : 가격, 수량엔 숫자만 가능
필드 검증 : 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 : 타입 에러 발생 시 빈 검증도 안되고 컨트롤러 호출도 안 됨
Comments