Validation이란?
유효성 검증으로 전달 받은 데이터 객체의 내용에서 잘못된 내용이 있는지 유효성을 확인합니다. Spring은 주로 다음 두 가지 방식으로 유효성 검증을 합니다.
- Java Bean Validation
- Spring validator 인터페이스 구현을 통한 Validation
해당 포스팅에서는 Java Bean 기반으로 간편하게 개별 데이터를 검증할 수 있는 Java Bean Validation에 대해 다루겠습니다. `Hibernate Validator'는 Bean Validation 표준의 구현체입니다.
의존성 추가
dependencies {
implementation 'org.hibernate.validator:hibernate-validator:6.2.0.Final'
}
제약 조건 정의
유효성 검증을 수행할 필드나 메서드 매개변수, 리턴 타입 등에 Hibernate Validator에서 제공하는 어노테이션을 사용하여 제약 조건을 정의해주면 됩니다.
public class User {
@NotBlank(message = "이름을 입력해주세요")
private String name;
@Min(0, "0보다 큰 수를 입력해주세요")
private int age;
@Email("이메일 형식이 아닙니다")
private String email;
@Size(min = 8, max = 20)
@Pattern(regexp="[a-zA-Z0-9]*")
private String password;
@Future(message = "유효기간: 금일부터 설정 가능")
private LocalDateTime expireDate;
}
- `@NotNull`: 해당 필드가 null이 아닌지를 검증
- `@NotEmpty`: 문자열, 컬렉션, 배열 등이 null이 아니고 비어 있는지 않은지 검증
- `@NotBlank`: 해당 필드의 값이 비어 있지 않고, 공백이 아닌지를 검증
- `@Size`: 해당 필드의 길이나 크기가 지정된 범위 내에 있는지를 검증
- `@Digits`: 숫자 값이 지정한 정수 및 소수 자릿수를 갖는지 검증
- `@Range`: 숫자 값이 지정한 범위 내에 있는지 검증
- `@Min`, `@Max`: 해당 필드의 값이 최소값 또는 최대값을 만족하는지를 검증
- `@Email`: 해당 필드의 값이 이메일 주소 형식에 맞는지를 검증
- `@Pattern`: 해당 필드의 값이 정규식 패턴과 일치하는지를 검증
- `@AssertTrue`, `@AssertFalse`: 해당 필드가 true 또는 false인지를 검증
- `@DecimalMin`, `@DecimalMax`: 해당 필드의 값이 지정된 범위 내의 십진수 값인지를 검증
Hibernate Validator에서 제약 조건 어노테이션에 `message` 속성을 지정하면 해당 제약 조건을 위반했을 때 지정한 메시지가 출력됩니다. 만약 `message` 속성을 지정하지 않으면 Hibernate Validator 내장된 기본 메시지를 사용합니다.
💁♂️둘 중 하나만 입력되면 통과하게 하는 방법!
이러한 어노테이션은 없지만 @AssertTure 어노테이션을 이용하면 기능을 구현할 수 있습니다.
해당 값이 true여야한다는 @AssertTrue을 이용하여 필드를 검사하는 방법입니다.
주의할 점
1) @Assert~ 어노테이션이 적용되기 위해서는 만드시 해당 메서드는 is로 시작해야합니다.
2) 값이 null인 경우 isBlank()에서 예외가 발생할 수 있기 때문에 nonNull은 먼저 체크해주어야 합니다.
아래는 name 혹은 id 둘 중 하나는 입력되어야 하는 코드입니다.@AssertTrue(message = "ID 혹은 NAME 둘 중 하나는 반드시 입력되어야 합니다.") public boolean isNameAndIdCheck() { if (Objects.nonNull(name) && !name.isBlank()) { return true; } if (Object.nonNull(id) && !id.isBlank()) { return true; } return false; }
유효성 검증 수행
위 예제코드처럼 코드를 작성해주고, @Valid 어노테이션을 해당 @RequestBody에 붙여주어, Java Bean Validation을 수행합니다.
@PostMapping("/insert")
public String insertUser(@Valid @RequestBody User user) {
// 유효성 검사 통과 시 수행 로직
return "유저 등록이 완료되었습니다.";
}
💁 검증 중 오류가 발생한다면? `MethodArgumentNotValidException` 발
`@Valid` 어노테이션은 유효성 검사 실패 시에 스프링에 `MethodArgumentNotValidException` 예외를 발생시킵니다. 이 예외가 발생하면 전에 요청을 중단시키고 클라이언트에게 에러 응답을 전송합니다.
유효성 검사가 실패한다면 예외가 발생하기 때문에 클라이언트에게 원하는 응답을 전송하지 못할 수 있습니다. 하지만 `BindingResult`를 사용하면 이 예외를 방지하고 컨트롤러에서 에러를 처리할 수 있습니다.
`BindingResult`를 함께 사용하면 MethodArgumentNotValidException 예외가 발생하지 않으며, 대신 컨트롤러에서 에러 처리 로직을 작성하여 요청을 중단시키지 않고 정상적으로 처리할 수 있습니다. 클라이언트에게 원하는 에러 메시지(응답)를 제공하거나 추가적인 로직을 수행할 수 있습니다.
@PostMapping("/insert")
public String insertUser(@Valid @RequestBody User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 바인딩 및 유효성 검사 에러 처리 로직
return "입력한 데이터가 유효하지 않습니다.";
}
// 유효성 검사 통과 시 수행 로직
return "유저 등록이 완료되었습니다.";
}
`BindingResult` 유효성 검증 오류가 있는 경우 `BindingResult` 매개변수에 담겨집니다. `bindingResult.hasErrors()` 메서드로 에러 여부를 확인하고, 있다면 에러 처리 로직을 수행할 수 있습니다. 용도에 따라 다양한 메서드가 있으므로 사용 시 도큐먼트를 참고하시기 바랍니다.
Data Binding
사용자 입력 데이터나 외부 서버의 요청 데이터를 특정 도메인 객체에 저장하거나 객체를 문자열로 변환하여 Request 담아주는 것을 말합니다. Data Binding은 데이터의 일관성과 정확성을 보장하고, 코드의 가독성을 향상시키는데 도움됩니다.
Data Binding하는 코드 예제를 보면서 설명을 이어하겠습니다.
Converter<S, T> 인터페이스
Spring에서 제공하는 인터페이스로, 하나의 타입(S)을 다른 타입(T)으로 변환하는 역할을 하는 인터페이스입니다.
Converter 구현:
@Component
public class XAppUserConverter implements Converter<String, XAuthUser> {
@Override
public XAppUser convert(String source) {
return objectMapper.readValue(source, XAppUser.class);
}
}
💁 Converter 등록하는 다른 방법
위 예제 코드에서는 Converter를 구현하고 @Component를 붙여주어 Spring Bean으로 등록해주었습니다.
@Component를 붙여주는 방법 외에도 WebConfig에 Converter 등록해주는 방법이 있습니다.
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new XAppUserConverter()); } }
Converter 사용 예제:
도메인)
public class XAppUser {
private String name;
private int age;
}
컨트롤러)
/**
* 요청
* GET /getAppUser
* x-app-user : {"name":"kim", "age":20}
*/
@GetMapping("/getAppUser")
public UserInfoResponse getUserInfo(@RequestHeader("x-app-user") XAppUser xAppUser) {
...
}
헤더에 담긴 json 형식 문자열을 특정 객체(XAppUser)에 바로 담아 Request로 전달할 수 있습니다.
💁 Converter 작동 원리
Converter를 만들어서 Bean으로 등록해주면 스프링 내에 내장된 서비스(ConversionService)에서 Converter 구현체 Bean들을 Converter 리스트에 등록합니다.
이렇게 되면 외부데이터가 들어왔을 때, Converter에 등록된 형식을 살펴보고 S type → T type이 일치하면 해당 Converter가 동작합니다.
Formatter
객체 ↔ String 간의 변화를 담당하는 인터페이스입니다. 주로 사용자에게 보여지는 데이터의 표시 형식을 지정하거나, 변환을 위해 사용됩니다.
Formatter 구현:
@Component
public class DateFormatter implements Formatter<Date> {
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date parse(String text, Locale locale) throws ParseException {
return formatter.parse(text); // 문자열을 Date로 파싱
}
@Override
public String print(Date object, Locale locale) {
return formatter.format(object); // Date를 문자열로 변환하여 반환
}
}
- parse 메소드:
문자열을 해당 타입으로 파싱하는 역할을 합니다. 주어진 문자열을 기반으로 해당 타입으로 변환하여 반환합니다. 파싱이 실패하면 ParseException을 던질 수 있습니다. - print 메소드:
데이터를 문자열로 변환하여 표시 형식을 만드는 역할을 합니다. 주어진 데이터를 문자열로 변환하여 반환합니다. 이 메소드는 주로 데이터를 표시할 때 사용됩니다.
💁 Formatter 작동 원리
Formatter도 Converter와 마찬가지로 Spring Bean으로 등록하면 자동으로 ConversionService에 등록시켜주기 때문에 필요(요청/응답 시 해당 데이터 타입이 있는 경우)에 따라 자동으로 동작하게 됩니다.
'Spring Boot' 카테고리의 다른 글
Spring Boot Validation @NotNull, @NotEmpty, @NotBlank 차이점 (0) | 2023.10.24 |
---|---|
SpringBoot에서 JUnit5로 효율적인 단위 테스트 작성하기, Assertions로 값 검증하기 (0) | 2023.10.04 |
SpringBoot에서 MockMvc을 활용한 컨트롤러, HTTP 요청 테스트 방법 (0) | 2023.10.04 |
Spring Scheduler를 활용한 일정 주기 스케줄링 작업 (0) | 2023.01.25 |
Spring Boot Log 남기기: Logback을 사용한 로그 전략 (0) | 2023.01.19 |