데이터에 대한 유효성 검증을 효과적으로 도와줄 수 있다.

bean-validation? hibernate-validator?

Bean-validation: JSR-380, 애노테이션을 이용하여 bean 유효성 검사를 위한 Java API 스펙

Hibernate-validator: Bean Validation을 구현한 Java API

Dependency

1
2
3
4
5
6
7
dependencies { 
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-validation')
compile('org.projectlombok:lombok')
runtime('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

Entity

애노테이션을 사용하면 쉽게 Entity에 대한 유효성 검사를 할 수 있다. 해당 애노테이션은 javax.validation.constraints패키지에 정의되어 있으며, 이를 아래와 같이 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class Member {

private Long idx;

@NotNull(message="name null")
private String name;

@Min(value=14, message="min 14")
private Integer age;

@NotNull(message="tel null")
private String tel;
}

@NotNull: null 검증
@Min, @Max: 최소값, 최대값 검증
@Size: 범위 검증
@Email: e-mail 검증
@AssertTrue: true 검증


@NotEmpty: null이나 size가 0 검증 (String, Collection)
@NotBlank: null이나 whitespace 검증 (String)
@Positive, @PositiveOrZero: 숫자 검증
@Negative, @NegativeOrZero: 숫자 검증
@Past, @PastOrPresent: 날짜 검증
@Future, @FutureOrPresent: 날짜 검증

Controller

검증하고자 하는 Entity에 @Valid를 붙이며, 이에 대한 결과를 받기 위해 BindingResult를 추가하여 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("member")
public class MemberController {

private final static Logger logger = Logger.getLogger(MemberController.class);
private final static int ZERO = 0;

//...

@PostMapping
public ResponseEntity<?> add(@Valid @RequestBody Member member, BindingResult bindingResult){
if(bindingResult.hasErrors()){
String errorMessage = bindingResult.getAllErrors().get(ZERO).getDefaultMessage();
return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(member, HttpStatus.OK);
}
}

Custom Validation

제공해주는 애노테이션도 많지만 사용자 정의 검증 애노테이션을 만들어야 하는 상황이 있을 수 있다.

@interface 정의
1
2
3
4
5
6
7
8
9
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ConstraintValidator 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
public class PhoneValidator implements ConstraintValidator<Phone, String> {

@Override
public void initialize(Phone phone) {

}

@Override
public boolean isValid(String field, ConstraintValidatorContext cxt) {
return field != null && field.matches("[0-9]+")
&& (field.length() > 8) && (field.length() < 14);
}
}
활용
1
2
@Phone
private String phone;

Test Code

자세한 코드는 GitHub을 참고

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
@RunWith(SpringRunner.class)
@WebMvcTest(MemberController.class)
public class MemberControllerTest {

@Autowired
private MockMvc mvc;

@Autowired
private ObjectMapper objectMapper;

// ...

@Test
public void test_success() throws Exception {
Member member = new Member(TEST_NAME, TEST_AGE, TEST_PHONE);
String memberToJson = objectMapper.writeValueAsString(member);

mockRequest(memberToJson, status().isOk(), memberToJson);
}

// ...

private void mockRequest(String memberToJson, ResultMatcher matcher, String result) throws Exception {
mvc.perform(post(TEST_END_POINT)
.content(memberToJson)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(matcher)
.andExpect(content().json(result));
}
}

참고

SpringBootSample / SpringBootValidator
Baeldung / spring-mvc-custom-validator