JAVA/spring

[1028web.com] Spring boot 2 Validation Example

GIT : github.com/miseongshin/diary/tree/forValidationInBlog


INDEX

 

1. @Valitated

  • CustomerController.java

2. Custom Validate Annotation

  • CustomerSignUpData.java
  • SameValue.java
  • SameValueValidator.java

3. Validation Annotation, BindingResult

  • CustomerController.java
  • CustomerSignUpData.java
  • DiaryValidErrorException.java
  • DiaryExceptionHandlers.java
  • ValidErrorData.java
  • ValidErrorResultData.java

4. Sequences(or order)

  • CustomerController.java
  • CustomerSignUpData.java
  • Sequences.java

 


1.  pom.xml 

org.hibernate.validator is  for the Validated field

com.google.cod.gson is for JUnit5

        <!-- valid annotation for  @Valid  -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>
        <!-- for json parameter for java bean  -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

2. CustomerController.java

@Valitated(Sequence.class) is for Sequences(or order)

@Validated(Sequences.class) @RequestBody CustomerSignUpData customerSignUpData, BindingResult bindingResult) throws DiaryValidErrorException

and

if (bindingResult.hasErrors()) {
throw new DiaryValidErrorException(bindingResult, messageSource);
}

are for @Valitated, BindingResult

    @PostMapping("/signUp/ajax")
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<Map> signUpAjax(HttpServletRequest request, @Validated(Sequences.class) @RequestBody CustomerSignUpData customerSignUpData, BindingResult bindingResult) throws DiaryValidErrorException {

        if (bindingResult.hasErrors()) {
            throw new DiaryValidErrorException(bindingResult, messageSource);
        }

        Map result = new HashMap<>();

        return ResponseEntity.ok(result);
    }

3. CustomerSignUpData.java

@SameValue(field = "password", fieldMatch = "confirmPassword") is for Custom Validation

groups= Sequences.OrderXX.class is Sequences(or order),

package com.today10sec.diary.customize.dto;

import com.today10sec.diary.customize.validator.SameValue;
import com.today10sec.diary.customize.validator.Sequences;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

@Setter
@Getter
@AllArgsConstructor
@ToString(exclude = {"password", "confirmPassword"})
@SameValue(field = "password", fieldMatch = "confirmPassword", groups= Sequences.Order6.class)
public class CustomerSignUpData {


    @NotEmpty(groups = Sequences.Order1.class)
    @Length(max=320, groups= Sequences.Order2.class)
    @Email(groups= Sequences.Order2.class)
    private String email;

    @NotEmpty(groups= Sequences.Order3.class)
    @Length(min= 6, max= 12, groups= Sequences.Order4.class)
    @Pattern(regexp="[a-zA-Z1-9]{6,12}",groups= Sequences.Order4.class)
    private String password;


    @NotEmpty(groups= Sequences.Order5.class)
    @Length(min= 6, max= 12, groups= Sequences.Order7.class)
    private String confirmPassword;

}

4. SameValue.java

this source for Custom Validation

package com.today10sec.diary.customize.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = SameValueValidator.class)
public @interface SameValue {

    String field();

    String fieldMatch();

    String message() default "{come.today10sec.diary.error.valid.samePassword}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

5. SameValueValidator.java

this source for Custom Validation

package com.today10sec.diary.customize.validator;

import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@Component
public class SameValueValidator implements ConstraintValidator<SameValue,Object> {

    private String field;
    private String fieldMatch;

    @Override
    public void initialize(SameValue constraintAnnotation) {
        this.field = constraintAnnotation.field();
        this.fieldMatch = constraintAnnotation.fieldMatch();

    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {

        String fieldValue = String.valueOf(new BeanWrapperImpl(object).getPropertyValue(field));
        String fieldMatchValue = String.valueOf(new BeanWrapperImpl(object).getPropertyValue(fieldMatch));

        if (fieldValue == null) {
            throw new RuntimeException("fieldValue can not be null");
        }else if(fieldMatchValue ==null ){
            throw new RuntimeException("fieldMatchValue can not be null");
        }

        if (fieldValue.equals(fieldMatchValue)){
            return true;
        } else{

            constraintValidatorContext.disableDefaultConstraintViolation();
            constraintValidatorContext.buildConstraintViolationWithTemplate("message")
                    .addPropertyNode(fieldMatch)
                    .addConstraintViolation();
            return false;
        }
    }
}

6. Sequences.java

this source for Sequences(or order)

package com.today10sec.diary.customize.validator;

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

@GroupSequence(value = {Sequences.Order1.class, Sequences.Order2.class, Sequences.Order3.class, Sequences.Order4.class, Sequences.Order5.class, Sequences.Order6.class, Sequences.Order7.class, Sequences.Order8.class, Sequences.Order9.class, Sequences.Order10.class, Default.class})
public interface Sequences {

    public @interface Order1 {
    }

    public @interface Order2 {
    }

    public @interface Order3 {
    }

    public @interface Order4 {
    }

    public @interface Order5 {
    }

    public @interface Order6 {
    }

    public @interface Order7 {
    }

    public @interface Order8 {
    }

    public @interface Order9 {
    }

    public @interface Order10 {
    }
}

7. DiaryValidErrorException.java

this source for Custom Validation BindingResult

package com.today10sec.diary.customize.exception;

import lombok.Getter;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * Validation Exception for Common Diary 
 */
@ResponseStatus(value= HttpStatus.BAD_REQUEST)
@Getter
public class DiaryValidErrorException extends Exception {

    private final BindingResult bindingResult;
    private final MessageSource messageSource;

    public DiaryValidErrorException(BindingResult bindingResult, MessageSource messageSource) {
        Assert.notNull(bindingResult, "BindingResult must not be null");
        Assert.notNull(bindingResult, "BindingResult must not be null");
        this.bindingResult = bindingResult;
        this.messageSource = messageSource;
    }
}

8. ValidErrorExceptionHandler.java

this source for Custom Validation, BindingResult

package com.today10sec.diary.config;

import com.today10sec.diary.customize.dto.ValidErrorData;
import com.today10sec.diary.customize.dto.ValidErrorResultData;
import com.today10sec.diary.customize.exception.DiaryValidErrorException;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.List;
import java.util.stream.Collectors;

@ControllerAdvice
public class DiaryExceptionHandlers {

    @ResponseBody
    @ExceptionHandler(DiaryValidErrorException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ValidErrorResultData ValidErrorExceptionHandler (DiaryValidErrorException diaryValidErrorException){

        MessageSource messageSource = diaryValidErrorException.getMessageSource();
        BindingResult bindingResult = diaryValidErrorException.getBindingResult();

        List<ValidErrorData> errorDataList = bindingResult.getFieldErrors()
                .stream().map(error-> new ValidErrorData(error, messageSource)
                ).collect(Collectors.toList());

        return new ValidErrorResultData(errorDataList);
    }
}

9. ValidErrorData.java

this source for Custom Validation, BindingResult

package com.today10sec.diary.customize.dto;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.validation.FieldError;

import java.util.Locale;

@Getter
public class ValidErrorData {

    Locale locale;

    private String fieldName;
    private String message;

    public ValidErrorData(FieldError fieldError, MessageSource messageSource){
        this.fieldName = fieldError.getField();
        this.message = messageSource.getMessage(fieldError.getCodes()[0],fieldError.getArguments(),fieldError.getDefaultMessage()+"["+fieldError.getCodes()[0]+"]",locale);
    }

    @Autowired
    public void setLocale(Locale locale) {
        this.locale = locale;
    }
}

10. ValidErrorResultData.java

this source for Custom Validation, BindingResult

package com.today10sec.diary.customize.dto;


import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.List;

@AllArgsConstructor
@Getter
public class ValidErrorResultData {
    private final Boolean validData = true;
    private List<ValidErrorData> errors;
}

 

11. CustomerControllerTest.java

this source for Custom Validation

package com.today10sec.diary.mvc.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.gson.Gson;
import com.today10sec.diary.customize.dto.CustomerSignUpData;
import com.today10sec.diary.customize.dto.ValidErrorResultData;
import com.today10sec.diary.customize.validator.SameValue;
import com.today10sec.diary.customize.validator.SameValueValidator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@ExtendWith(SpringExtension.class)
@WebMvcTest({CustomerController.class, SameValueValidator.class, SameValue.class, CustomerSignUpData.class})
class CustomerControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    void SIGN_UP_AJAX_VALID_CONFIRM_PW() throws Exception {

        String expectedResult = "{\"validData\":true,\"errors\":[{\"locale\":null,\"fieldName\":\"confirmPassword\",\"message\":\"비밀번호와 비밀번호확인값이 일치하지 않습니다. \"}]}";
        CustomerSignUpData customerSignUpData = new CustomerSignUpData("a@a.com","111111","222222" );
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
        ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
        String requestJson=ow.writeValueAsString(customerSignUpData);

        MvcResult mvcResult= mockMvc.perform(
                post("/customer/signUp/ajax")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .content(requestJson))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andReturn();

        String result = mvcResult.getResponse().getContentAsString();
        Gson gson = new Gson();
        ValidErrorResultData resultData = gson.fromJson(result, ValidErrorResultData.class);
        Assertions.assertEquals("confirmPassword",resultData.getErrors().get(0).getFieldName(),"confirmPassword not contian");

    }

}

 

 

>> 도움이 많이 되었는데 예전버전이라 이것저것하면서 적용

***Spring REST Validation Example mkyong.com/spring-boot/spring-rest-validation-example

**Spring Boot Validation 순서 정하기 & 테스트 코드 dncjf64.tistory.com/302

www.baeldung.com/spring-mvc-custom-validator

mkyong.com/java/how-do-convert-java-object-to-from-json-format-gson-api/