티스토리 뷰

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(소수)자리수를 갖는 숫자인지 확인
@Email 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
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함