728x90
Validation 필요한 이유
- 유효성 검증 하는 코드의 길이가 너무 길다. -> annotation 으로 해결
- service logic에 대해서 방해가 된다.
- 흩어져 있는 경우 어디서 검증 되었는지 찾기 힘들다.
- 검증 로직이 변경되는 경우 테스트 코드 등, 전체 로직이 흔들릴 수 있다. -> 한 곳에 몰아서 검증 가능
JSR-380 BeanValidation
build.gradle.kts 세팅
DeleteApiController
// https://beanvalidation.org/2.0-jsr380/spec/
// JSR-320
// hibernate Validation
// Spring boot Validation
@RestController
@RequestMapping("/api")
@Validated //_age는 Bean이 아니기 때문에 어노테이션 필요
class DeleteApiController {
// 가질수 있는 것
// 1. path variable
// 2. request param
@DeleteMapping(path = ["/delete-mapping"])
fun deleteMapping(
// 이름 지정 가능
@RequestParam(value = "name") _name: String,
//Validation
@NotNull(message = "age 값이 누락되었습니다.")
@Min(20, message = "20보다 커야 합니다.")
@RequestParam(name = "age") _age: Int
): String {
println(_name)
println(_age)
return _name + " " + _age
}
@DeleteMapping(path = ["/delete-mapping/name/{name}/age/{age}"])
fun deleteMappingPath(
@PathVariable(value = "name")
@Size(min = 2, max = 5, message = "name의 길이는 2~5")
@NotNull
_name: String, // aa ~ aaaaa
//Validation
@NotNull(message = "age 값이 누락되었습니다.")
@Min(20, message = "20보다 커야 합니다.")
@PathVariable(name = "age") _age: Int
): String {
println(_name)
println(_age)
return _name + " " + _age
}
}
PutApiController
@RestController
@RequestMapping("/api")
class PutApiController {
@PutMapping("/put-mapping")
fun putMapping(): String {
return "put-mapping"
}
@RequestMapping(method = [RequestMethod.PUT], path = ["/request-mapping"])
fun requestMapping(): String {
return "request-mapping - put method"
}
//Post와 동일, Put -> 내용없으면 생성, 있으면 수정
//Bean에 Vaildation 적용할려면 @Vaild 필요
//BindingResult -> Bean 에러 분기 나눠서 검출
@PutMapping(path = ["/put-mapping/object"])
fun putMappingObject(
@Valid @RequestBody userRequest: UserRequest,
bindingResult: BindingResult //Vaild -> BindingResult -> hasError? -> logic 탄다
): ResponseEntity<String> {
if (bindingResult.hasErrors()) {
// 500 error
val msg = StringBuilder()
bindingResult.allErrors.forEach {
val field = it as FieldError // FieldError 로 형변환
val message = it.defaultMessage //Message Customize
msg.append("${field.field} : $message\n") //메세지 합치기
// name : 크기가 2에서 8 사이여야 합니다
// name : 비어 있을 수 없습니다
// age : 0 이상이어야 합니다
}
return ResponseEntity.badRequest().body(msg.toString())
}
return ResponseEntity.ok("")
}
}
Custom Validation Annotation
UserRequeset
//해당 Bean 검증 (UserRequest)
data class UserRequest(
@field:NotEmpty
@field:Size(min = 2, max = 8)
var name: String? = null,
@field:PositiveOrZero // 0 < 숫자를 검증 0도 포함(양수)
var age: Int? = null,
@field:Email // email양식
var email: String? = null,
@field:NotBlank // 공백을 검증
var address: String? = null,
@field:Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}\$") // 정규식 검증
var phoneNumber: String? = null, // phoneNumber 양식
//Valid 까다로울 때, 원하는 조건의 어노테이션 없을 경우 -> annotation 커스터마이징
@field:StringFormatDateTime(pattern = "yyyy-MM-dd HH:mm:ss", message = "패턴이 올바르지 않습니다.")
var createdAt: String? = null // yyyy-MM-dd HH:mm:ss ex) 2020-10-02 13:00:00
)
//{
//True면 정상 False면 비정상 -> 매번 이렇게 만들긴 힘들어서 커스텀 어노테이션 만든다 보통 : annotation package, StringFormatDateTime 참고
// @AssertTrue(message = "생성일자의 패턴은 yyyy-MM-dd HH:mm:ss 여야 합니다") //method는 field X
// private fun isValidCreatedAt(): Boolean { // 정상 -> true , 비정상 -> false
// return try {
// LocalDateTime.parse(this.createdAt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
// true
// } catch (e: Exception) {
// false
// }
// }
//}
StringFormatDateTime
//Annotation으로써 작동하게 하는 Setting
//Validator필요 -> StringFormatDateTimeValidator
@Constraint(validatedBy = [StringFormatDateTimeValidator::class])
@Target(
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME) //Runtime 에만 활용 할 수 있도록
@MustBeDocumented //Kotlin에서 붙여주기
annotation class StringFormatDateTime(
//pattern을 받고 message를 출력할 수 있다.
val pattern: String = "yyyy-MM-dd HH:mm:ss",
val message: String = "시간형식이 유효하지 않습니다",
//default 값
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
StringFormatDateTimeValidator
class StringFormatDateTimeValidator : ConstraintValidator<StringFormatDateTime, String> {
private var pattern: String? = null
//Annotation pattern 불러오기
override fun initialize(constraintAnnotation: StringFormatDateTime?) {
this.pattern = constraintAnnotation?.pattern
}
// 검증할때의 메소드 : 정상이면 True, 비정상이면 False
override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
return try {
LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern))
true
} catch (e: Exception) {
false
}
}
}
728x90
'ETC.. > Spring' 카테고리의 다른 글
[Spring] Spring Boot로 ToDoApp만들기(투두 앱) (1) (0) | 2021.04.05 |
---|---|
[Spring] Spring Boot Junit 단위 테스트 설명 & 예제 (Kotlin) (0) | 2021.04.04 |
[Spring] Spring Boot 예외 처리 설명 & 예제 (Kotlin) (0) | 2021.03.17 |
[Spring] SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet (0) | 2021.03.16 |
[Spring] Java를 이용한 간단한 CRUD API만들기 (0) | 2021.01.29 |