처음 시작은 게시판의 게시글을 post 하기 위한 DTO를 작성하고 요청을 보내 테스트하던중 UnexpectedTypeException 예외가 발생하였다.
(해당 예외 이름에서 알 수 있듯이 요청으로 받은 JSON 파일이 다른 타입을 제공하고 있어서 발생한 예외이다. 이것만 좀더 일찍알았더라면 2시간을 허비하지 않았을텐데...)
발생한 예외의 DTO와 Enum 코드
DTO
public class PostsDto {
@Getter
@Builder
@Setter
public static class Post{
...
...
@NotBlank
private String title;
@NotBlank // 예외 발생지점
private Posts.PostStatus postStatus;
@NotBlank
private String text;
}
}
Enum
public enum PostStatus{
POST_PUBLIC("public post"),
POST_PRIVATE("private post"),
POST_DELETED("deleted post");
@Getter
private String status;
PostStatus(String status) {
this.status = status;
}
}
요청 JSON
{
"text": "the Contents",
"title":"the Title",
"postStatus": "public post"
}
구글링을 해보니 JSON 포맷은 숫자형과 문자형으로만 타입이 정해저 있다보니 Spring MVC의 HandlerAddapter에서 맵핑이 안되 발생하는 예외이므로 역직렬화를 위한 코드수정이 필요했다.
1차 시도 - Enum 코드수정
public enum PostStatus{
...
...
@JsonCreator
public static PostStatus from(String sub) {
for (PostStatus status : PostStatus.values()) {
System.out.println(status.getStatus());
if (status.getStatus().equals(sub)) {
return status;
}
}
return null;
}
@JsonValue
public String getStatus() {
return status;
}
}
위와 같이 역직렬화를 위한 코드를 추가해 주었지만 같은 예외가 계속 발생하였다.
무엇이 문제였을까...
해당 DTO의 예외발생지점의 @NotBlank validator를 지워 보았더니 잘 작동하였다.
하지만 내가 원한건 유효성검증이 잘 이뤄지면서 해당 요청을 받아야 했기에 또 다시 구글링 시작... 이후 새로운 validator를 만들어 보았다.
2차 시도 - 커스텀 유효성 검증 에너테이션 추가
@Documented
@Constraint(validatedBy = EnumValidatorConstraint.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE})
@NotNull
public @interface EnumValidator {
Class<? extends Enum<?>> enumClass();
String message() default "must be any of enum {enum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {
Set<String> values;
@Override
public void initialize(EnumValidator constraintAnnotation) {
System.out.println(constraintAnnotation.enumClass());
values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toSet());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return values.contains(value);
}
}
위와 같이 유효성검증을 위해 새로운 에너테이션을 만들어준 뒤 해당 에너테이션을 예외발생지점에 명시해 주었다.
@EnumValidator(enumClass = Posts.PostStatus.class)
private Posts.PostStatus postStatus;
그리고 다시 테스트를 해보았는데... 잉??? 또 다시 같은 예외가 발생했다??
결국 구글링을 다시 해보았는데... 정답은 바로 예외 메세지에 담겨 있었다...
@NotBlank 사용중 PostStatus라는 허락되지 않은 타입이 정의되있어서 발생한 예외인것인데
@NotBlank와 @NotEmpty는 문자형, 배열 혹은 컬렉션 타입에서만 사용이 가능하다.
분명 어딘가에서 읽었던 내용인데 무심코 지나친 대가가 크구나...
결론
@NotNull의 경우 모든 타입이 사용가능하며 String타입만 지정해 주지 않는다면 Empty 값이 들어올 수 없으므로 @NotNull을 사용하자.
결과 코드
DTO
public class PostsDto {
@Getter
@Builder
@Setter
public static class Post{
...
...
@NotNull // @NotNull 사용
private Posts.PostStatus postStatus;
...
...
}
}
Enum
public enum PostStatus{
POST_PUBLIC("public post"),
POST_PRIVATE("private post"),
POST_DELETED("deleted post");
private String status;
PostStatus(String status) {
this.status = status;
}
// @NotNull을 사용하더라도 @JsonValue를 이용하여 해당 Enum에 대한 getter를 만들어 주어야 한다.
@JsonValue
public String getStatus() {
return status;
}
}
'Projects > Personal project - 게시판 만들기' 카테고리의 다른 글
MockMVC - Multipart form data요청 테스트 (0) | 2023.06.10 |
---|---|
Mock test 단계에서 발생한 MockHttpServletResponse: body is empty (0) | 2023.06.08 |
파일 업로드를 위한 AWS S3 연결 도중 발생한 문제들과 해결방안 (0) | 2023.06.07 |
JPQL문에서 Enum 사용하기 (0) | 2023.05.30 |
DB연동 도중 발견된 버그들(Enumerated, AuditingEntityListener, Optional 메서드) (0) | 2023.05.22 |