티스토리 뷰
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 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 |