보고 배운 사이트 : mangkyu.tistory + ChatGPT + 인강
객체의 유효성을 검증하기 위해 사용되는 어노테이션이다.
이것들은 주로
클래스의 필드
나, 메서드 매개변수
에 적용하여
데이터의 유효성을 정의하고 검사한다.
다양한 검증 어노테이션들이 있으며,
몇 가지 대표적인 어노테이션들을 살펴본다.
null
불가를 의미
null
, ""
불가를 의미
null
, ""
, " "
불가를 의미
int Type 불가
문자 길이 측정
@Size(min = 6, max = 6)
과거 날짜
오늘이거나 과거 날짜
미래 날짜
오늘이거나 미래 날짜
별도 Logic 적용
재사용이 불가능하다.
해당 로직이 필요한 다른 DTO 클래스가 있다면,
거기에도 똑같이 작성해주어야 하는 단점이 있음.
@AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.") // true가 반환되면 통과 . 메서드 이름이 is로 시작해야됨
public boolean isReqYearMonthValidation() {
try {
LocalDate localDate = LocalDate.parse(this.reqYearMonth + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
} catch (Exception e) {
return false;
}
return true;
}
정규 표현식(pattern)에 맞아야 함을 나타냄.
숫자 값이 주어진 최솟값(min) 이상이어야 함을 나타냄.
숫자 값이 주어진 최댓값(max) 이하여야 함을 나타냄.
문자열이 유효한 이메일 주소 형식이어야 함을 나타냄.
해당 Object Validation 실행
JSR-380 ( Bean Validation 2.0 ~ 3.0 ) 스펙에 정의된 표준 어노테이션이다.
( Bean Validation 1.0 은 JSR-303 )
이 어노테이션은
객체의 필드나 매개변수에 부착될 수 있으며, 해당 객체의 검증을 수행한다.
컨트롤러의 매개변수에 사용하여
요청 데이터의 유효성을 검증하고
검증 결과에 따라 적절한 처리를 수행하는데 활용되거나,
DTO에 사용하여
객체의 유효성을 검증하는 용도로 사용한다.
@Valid 어노테이션은
검증을 수행하기 위해 Validator 인터페이스를 구현한 검증기(validator)를 사용한다.
Spring Boot에서는 주로 Hibernate Validator를 검증기로 사용한다.
Hibernate Validator는
Bean Validation 스펙의 구현체로, 다양한 검증 어노테이션을 지원하고,
커스텀 검증 로직을 작성할 수 있다.
사용자 정의 검증 어노테이션은
구현되어있지 않은 필요한 검증을 작성할 경우,
그리고
@AssertTrue / @AssertFalse 어노테이션을 이용한 검증 메서드를
여러 파일에서 사용해야 할 경우에
메서드를 사용자 정의 어노테이션으로 만들어서 사용하면
유용하게 사용될 수 있다.
assertTrue/False로 구현한 메서드는 재사용이 불가능하기 때문이다.
년도
와 월
만 입력받아야 하는 경우를 검증하기위한 어노테이션 생성
검증 형태 : yyyyMM
- YearMonth.java
- YearMonthValidator.java
우선 , 어노테이션 파일을 생성한 후
다른 어노테이션에서 필요한 설정값을 가져온다.
아무 검증 어노테이션 파일을 열어서,
@Constraint
, @Target
, @Retention
어노테이션과 ,
message()
, groups()
, payload()
메서드를 복사해서 가져온다.
가져온 후
message() 메서드의 default 값으로
검증 실패시 보여줄 문구를 입력해주면 좋다.
그리고 이번 실습에서 검증 형태를 지정해주기 위해
pattern() 메서드를 추가로 만들어준다.
이 때 , default로 검증 형태를 입력해두면 좋다.
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface YearMonth {
String message() default "{jakarta.validation.constraints.Email.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
// 문자열 형태 검열을 위해 추가해준 메서드
String pattern() default "yyyyMM";
}
이 검증 파일은,
어노테이션 파일의 @Constraint
에 입력해줄 파일이다.
파일 이름은 YearMonthValidator
라고 작성해주었고,
파일 형태는 Class 파일이다.
파일 생성 후
ConstraintValidator<YearMonth, String>
인터페이스를 상속받아준다.
이 때,
YearMonth는 검증값을 입력 받을 어노테이션 이름이고
String은 입력받을 값의 자료형이다.
인터페이스를 상속받고
initialize() 메서드와 , isValid() 메서드를 Override 해서 내용을 작성해준다.
initialize()
메서드에는 초기화가 필요한 경우,
어노테이션에서 메서드를 불러와서 사용할 수 있다.
isValid()
메서드에서는 검증할 내용을 작성해주면 된다.
public class YearMonthValidator implements ConstraintValidator<YearMonth, String> {
private String pattern;
@Override
public void initialize(YearMonth constraintAnnotation) {
this.pattern = constraintAnnotation.pattern();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// this.pattern = yyyyMM
// value = 검증할 사용자 입력 값
try {
LocalDate localDate = LocalDate.parse(value + "01", DateTimeFormatter.ofPattern(this.pattern + "dd"));
} catch (Exception e) {
return false;
}
return true;
}
}
Validator 파일을 작성 완료했다면,
다시 YearMonth 어노테이션 작성 파일로 돌아와서
상단의 @Constraint 메서드에 작성한 Validator 파일이름을 입력해준다.
@Constraint(validatedBy = {YearMonthValidator.class})
...
어노테이션 사용은 다른 어노테이션 사용하듯이 하면 됨!
public Class User {
@YearMonth
private String reqYearMonth;
}
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Getter
@Setter
@ToString
public class Users {
private String name;
@Min(value = 0)
@Max(value = 90, message = "나이 제한 사항 : 90세 이하만 가능")
private int age;
@Email
private String email;
private String phoneNumber;
@Valid
List<Car> cars;
}
@Getter
@Setter
@ToString
public class Car {
@NotBlank
private String name;
}
Controller에서
RequestBody 값을 입력 받을 메서드에
@Valid
어노테이션을 붙여주어야 한다.
@RestController
@RequestMapping("/validation")
public class ValidationController {
@PostMapping("/user")
public ResponseEntity user(@Valid @RequestBody Users user) {
System.out.println(user);
return ResponseEntity
.status(HttpStatus.OK)
.body(user);
}
}
spring boot log message
2023-06-06T15:15:17.567+09:00
WARN 15708 --- [nio-8888-exec-1]
.w.s.m.s.DefaultHandlerExceptionResolver :
Resolved [org.springframework.web.bind.MethodArgumentNotValidException:
Validation failed for argument [0] in
public org.springframework.http.ResponseEntity com.example.partthree.controller.ValidationController.user(com.example.partthree.validation.dto.Users):
[Field error in object 'users' on field 'email': rejected value [asdasd];
codes [Email.users.email,Email.email,Email.java.lang.String,Email];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [users.email,email];
arguments [];
default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@486472a5,.*];
default message [올바른 형식의 이메일 주소여야 합니다]] ]
@Valid
어노테이션을 사용한 메서드에서
BindingResult 를 사용하면
결과값이 BindingResult
로 들어온다.
@PostMapping("/user")
public ResponseEntity user(@Valid @RequestBody Users user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
StringBuilder sb = new StringBuilder();
bindingResult.getAllErrors().forEach(objectError -> {
FieldError field = (FieldError) objectError;
String message = objectError.getDefaultMessage();
sb.append("\nfield : ").append(field.getField())
.append("\nmessage : ").append(message);
});
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(sb.toString());
}
System.out.println(user);
return ResponseEntity
.status(HttpStatus.OK)
.body(user);
}
에러 발생확인
bindingResult.hasErrors()
발생한 모든 에러에 대한 정보 조회
List<ObjectError> bindingResult.getAllErrors()
FieldError 정보 조회를 위한 형 변환
FieldError field = (FieldError) objectError;
field 이름 정보 조회
field.getField()
오류 메세지 조회
objectError.getDefaultMessage()
spring boot 에러 로그는 출력되지 않음.
String name, int age 등등
Dto클래스를 사용하지 않고 넘어오는 데이터를 검증하는 방법!
@RestController
@RequestMapping("/exception")
@Validated
public class ExceptionController {
...
}
@GetMapping
public UserExcept get(
@Size(min = 1)
@RequestParam String name
,
@NotNull
@RequestParam Integer age) {
...
}
spring boot err log
org.springframework.web.bind.MissingServletRequestParameterException