티스토리 뷰
Validation
spring-boot-starter-validation은 hibernate-validator, jakarta.validation을 포함합니다.
위의 Reference링크에 있는 가이드에 따르면 Hibernate Validator는 Jakarta Bean Validation(JSR380)의 참조구현이라고 합니다.
Hibernate Validator 6 또는 Bean Validation2.0을 사용하기 위해서는 JDK 8 이상이 필요합니다.
Reference
https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/
Hibernate Validator 6.0.22.Final - JSR 380 Reference Implementation: Reference Guide
Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th
docs.jboss.org
Hibernate Validator Maven 의존성
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.22.Final</version>
</dependency>
제약 적용하기
package org.hibernate.validator.referenceguide.chapter01;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
public Car(String manufacturer, String licencePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
}
//getters and setters ...
}
@NotNull, @Size, @Min 주석이 제약조건을 선언합니다.
manufacturer : null일 수 없습니다
licensePlate : 2와 14자 사이여야합니다.
seatCount : 2 이상이어야 합니다.
Validation 구현
유효성 검사 예제
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class CarTest {
private static Validator validator;
@BeforeClass
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void manufacturerIsNull() {
Car car = new Car( null, "DD-AB-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
}
@Test
public void licensePlateTooShort() {
Car car = new Car( "Morris", "D", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"size must be between 2 and 14",
constraintViolations.iterator().next().getMessage()
);
}
}
Bean Validation
Field Validation
@NotNull
private String manufacturer;
@AssertTrue
private boolean isRegistered;
Method Validation
@NotNull
public String getManufacturer() {
return manufacturer;
}
@AssertTrue
public boolean isRegistered() {
return isRegistered;
}
Object Graph (계단식 검증)
@Valid 어노테이션을 계단식 검증을 위한 매개변수에 사용할 수 있습니다.
package org.hibernate.validator.referenceguide.chapter02.objectgraph;
public class Car {
@NotNull
@Valid
private Person driver;
//...
}
package org.hibernate.validator.referenceguide.chapter02.objectgraph;
public class Person {
@NotNull
private String name;
//...
}
Validator in spring
gradle 의존성
// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
implementation 'org.springframework.boot:spring-boot-starter-validation'
error handling
Object Graph @Valid 어노테이션을 이용해 User 객체의 유효성을 검사합니다.
...
@PostMapping("/users")
ResponseEntity<String> addUser(@Valid @RequestBody User user) {
...
@ExceptionHandler 어노테이션을 이용해 Validation을 통과하지 못했을 때 에러를 처리합니다.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
...
public String createUser(@Valid User user, Errors errors) throws Exception {
if(errors.hasErrors()) {
return "userForm";
}
...
example
Controller
package com.example.demo.controller;
import com.example.demo.entity.ValidEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api")
public class ValidController {
@PostMapping(value="/send")
public ResponseEntity valid(
@RequestBody @Valid ValidEntity entity
) {
return ResponseEntity.ok("success");
}
}
Exception
package com.example.demo.controller;
import com.example.demo.entity.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@ControllerAdvice
@RestController
public class ExceptionController {
/**
* @valid 유효성체크에 통과하지 못하면 MethodArgumentNotValidException 이 발생한다.
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity methodValidException(MethodArgumentNotValidException exception, HttpServletRequest request){
BindingResult bindingResult = exception.getBindingResult();
StringBuilder builder = new StringBuilder();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
builder.append("[");
builder.append(fieldError.getField());
builder.append("](은)는 ");
builder.append(fieldError.getDefaultMessage());
builder.append(" 입력된 값: [");
builder.append(fieldError.getRejectedValue());
builder.append("]");
builder.append("\n");
}
ErrorResponse errorResponse = new ErrorResponse("Validation Error", "입력값이 올바르지 않습니다", builder.toString())
val errorMap: MutableMap<String, Any> = mutableMapOf()
bindingResult.fieldErrors.forEach {
errorMap[it.field] = mapOf("defaultMessage" to it.defaultMessage, "isError" to true)
}
errorResponse.data = errorMap
return new ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST);
}
}
error response
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class ErrorResponse {
private String code;
private String description;
private String detail;
private Map<String, Object> data;
public ErrorResponse(String code, String description) {
this.code = code;
this.description = description;
}
}
메서드 변수 제약 선언
public RentalStation(@NotNull String name) {
//...
}
public void rentCar(
@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays) {
//...
}
제약조건은 instance method에만 적용됩니다. static method에는 지원하지 않습니다.
default message interpolation(보간)
@NotNull(message = "The manufacturer name must not be null")
private String manufacturer;
메시지 표현식을 이용한 보간
@Size(
min = 2,
max = 14,
message = "The license plate '${validatedValue}' must be between {min} and {max} characters long"
)
private String licensePlate;
@Min(
value = 2,
message = "There must be at least {value} seat${value > 1 ? 's' : ''}"
)
private int seatCount;
@DecimalMax(
value = "350",
message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher " +
"than {value}"
)
private double topSpeed;
@DecimalMax(value = "100000", message = "Price must not be higher than ${value}")
private BigDecimal price;
test validation entity
package com.example.demo.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Setter
@Getter
@ToString
public class ValidEntity {
@DecimalMin(value="0.5", message = "최소값은 0.5 입니다")
Float decimalMin;
@DecimalMax(value="1.5", message = "최대값은 1.5 입니다")
Float decimalMax;
@Min(value = 5, message = "최소값은 5입니다")
Integer intMin;
@Max(value = 50, message = "최대값은 50입니다")
Integer intMax;
@Size(min = 2, max = 10, message = "2~30 글자를 입력해주세요")
String StrSize;
@NotNull(message = "값을 입력해주세요")
String notNull;
@NotEmpty(message = "문자를 입력해주세요")
String notEmpty;
@Positive(message = "양수를 입력해주세요")
Integer positive;
@PositiveOrZero(message = "0을 포함한 양수를 입력해주세요")
Integer positiveOfZero;
@Negative(message = "음수를 입력해주세요")
Integer negative;
@NegativeOrZero(message = "0을 포함한 음수를 입력해주세요")
Integer negativeOfZero;
@Email(message = "email 양식에 맞게 입력해주세요")
String email;
}
어노테이션 | 속성 | 타입 | 설명 |
@AssertFalse | Boolean, boolean | 요소가 False인지 확인 | |
@AssertTrue | Boolean, boolean | 요소가 True인지 확인 | |
@DecimalMax | value, inclusive | BigDecimal, BigInteger, CharSequance, byte, short, int, long 및 각 primitive 의 wrapper | inclusive = false인 경우 value보다 작은지 확인 그렇지 않으면 작거나 같은지 확인 |
@DecimalMin | value, inclusive | BigDecimal, BigInteger, CharSequance, byte, short, int, long 및 각 primitive 의 wrapper | inclusive = false 일경우 value보다 큰지 체크 아닐경우 크거나 같은지 확인 |
@Degits | integer, fraction | BigDecimal, BigInteger, CharSequance, byte, short, int, long 및 각 primitive 의 wrapper | integer자릿수와 fraction(소수)자리수를 갖는 숫자인지 확인 |
regexp, flags | CharSequence | 유효한 이메일 주소인지 확인 | |
@Future | java.util.Date, java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate | 날짜가 미래인지 확인 | |
@FutureOrPresent | java.util.Date, java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate | 주석이 달린 날짜가 현재인지 미래인지 확인 | |
@Max | value | BigDecimal, BigInteger, byte, short, int, long | 지정된 최소값보다 크거나 같은지 확인 |
@NotBlank | CharSequence | 문자열이 NULL이 아니고 길이가 0보다 큰지 확인 | |
@NotEmpty | CharSequence, Collection, Map 및 배열 | null 또는 비어있는지 확인 | |
@NotNull | null을 허용하지 않음 | ||
@Negative | BigDecimal, BigInteger, byte, short, int, long | 음수인지 확인 | |
@NegativeOrZero | BigDecimal, BigInteger, byte, short, int, long | 음수 또는 0인지 확인 | |
@Null | Null인지 확인 | ||
@Past | java.util.Date, java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate | 날짜가 과거인지 확인 | |
@PastOrPresent | java.util.Date, java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate | 날짜가 과거인지 현재인지 확인 | |
@Pattern | regex, flags | CharSequence | 정규식과 일치하는지 확인 |
@Positive | BigDecimal, BigInteger, byte, short, int, long | 양수인지 확인 0은 유효하지 않음 | |
@PositiveOrZero | BigDecimal, BigInteger, byte, short, int, long | 양수또는 0인지 확인 | |
@Size | min, max | CharSequence, Collection, Map, 배열 | 요소의 크기가 min~max(포함)인지 확인 |
그외 추가제약은 Reference에서 참조
@CreditCardNumber
@Currentcy
@DurationMax
@DurationMin
@EAN
@ISBN
@Length
@CodePointLength
@LuhnCheck
@Mod10Check
@Mod11Check
@Range
@SafeHtml
@ScriptAssert
@UniqueElements
@URL
Validation, Validate, Validate, Valid
'스프링' 카테고리의 다른 글
JMeter 웹서버 부하테스트 (0) | 2021.02.05 |
---|---|
타임리프(thymeleaf) 사용하기 (0) | 2020.12.31 |
JAVA SPRING 인터셉터 (Interceptor) (0) | 2018.07.26 |
JAVA SPRING 트랜잭션(Transaction) (0) | 2018.07.26 |
AOP (Aspect Oriented Programming) (0) | 2018.07.25 |