[Spring] @RequestBody DTO를 요청받을 때 DTO속 필드 List<?>의 유효성 검증이 필요하다면,

Walter Mitty·2024년 4월 18일
0

서론

⚙️ 프로젝트 스펙

  • Java 17
  • Spring Boot 3.2.4
  • Multi Module
  • Hexagonal Architecture

상황

Controller 단에서 필요한 정보를 DTO에 담아(@RequestBody 사용) 받는 중이다.

유효성 검증을 위해 @Valid를 붙이고, DTO 내부의 각 필드마다 맞는 유효성검증 애너테이션을 사용중인데
문자열이나 숫자를 위한 유효성 검사용 애너테이션은 잘 사용하고 있지만 List<?> 필드가 있다면 이에 맞는 유효성 검증은 어떻게 이루어 져야 할까?
(e.g. List<Long>, List<SongDTO>...)

본론

코드

StudentAccountController.java

@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/admin-account/student", name = "[관리자모드]학생 관리", produces = "application/json")
public class StudentAccountController {

    private final StudentAccountUseCase studentAccountUseCase;

    @DupleCheck
    @PostMapping(name = "학생 추가")
    public BaseResponse<Object> add(@AuthToken String token, @Valid @RequestBody StudentAccountDTO.RegisterRequest request) {
        studentAccountUseCase.registerStudent(token, request.toCommand());
        return BaseResponse.ofSuccess("");
    }

StudentAccountDTO.java

전체 코드가 아니라 필요한 부분만 보고 싶으시다면 밑으로!

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class StudentAccountDTO {

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class RegisterRequest {

        @NotBlank(message = Expression.MUST_INPUT_REQUEST_MSG)
        @Pattern(regexp = Expression.USER_NAME_REG_EXP, message = Expression.USER_NAME_MSG)
        private String name;

        @NotBlank
        @Pattern(regexp = Expression.STUDENT_NUMBER_REG_EXP, message = Expression.STUDENT_NUMBER_MSG)
        private String studentNumber;

        @NotNull
        @Email
        private String email;

        @Pattern(regexp= Expression.USER_PHONE_NUM_REG_EXP, message= Expression.PHONE_NUM_MSG)
        private String phoneNum;

        @NotBlank
        private String registrationStatus;

        @Pattern(regexp = Expression.SCHOOL_YEAR_REG_EXP, message = Expression.SCHOOL_YEAR_MSG)
        private String schoolYear;

        private List<@Min(1) Long> courseInfoIdList;

        public StudentAccountCommand.Register toCommand() {
            return StudentAccountCommand.Register.builder()
                    .name(name)
                    .studentNumber(studentNumber)
                    .email(email)
                    .phoneNum(phoneNum)
                    .registrationStatus(RegistrationStatus.from(registrationStatus))
                    .schoolYear(DthStringUtil.removeStr(schoolYear))
                    .courseInfoIdList(courseInfoIdList)
                    .build();
        }
    }
}

StudentAccountDTO.java

뜯어보자.

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class StudentAccountDTO {

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class RegisterRequest {
		@NotBlank(message = Expression.MUST_INPUT_REQUEST_MSG)
        @Pattern(regexp = Expression.USER_NAME_REG_EXP, message = Expression.USER_NAME_MSG)
        private String name;
		.
		.
		(중략)
		.
		.
		// @Min(1)을 여기, 위에 붙이는게 아니라 List<> 안의 변수 타입 앞에 붙여주면 된다.
        private List<@Min(1) Long> courseInfoIdList;

        public StudentAccountCommand.Register toCommand() {
            return StudentAccountCommand.Register.builder()
                    .name(name)
                    .studentNumber(studentNumber)
                    .email(email)
                    .phoneNum(phoneNum)
                    .registrationStatus(RegistrationStatus.from(registrationStatus))
                    .schoolYear(DthStringUtil.removeStr(schoolYear))
                    .courseInfoIdList(courseInfoIdList)
                    .build();
        }
    }
}

만약 변수타입을 List<SongDTO>처럼 어떤 커스텀 오브젝트를 쓴다면,

코드

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SongDTO {

    @Getter
	@Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class RegisterRequest {
		@Min(1)
        private Long courseInfoId;

    }
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class StudentAccountDTO {

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class RegisterRequest {
		@NotBlank(message = Expression.MUST_INPUT_REQUEST_MSG)
        @Pattern(regexp = Expression.USER_NAME_REG_EXP, message = Expression.USER_NAME_MSG)
        private String name;
		.
		.
		(중략)
		.
		.
		@Valid // 여기에 Valid를 붙여야 SongDTO 내부에서 유효성검증이 가능하다.
        private List<SongDTO> songDtoList;

        public StudentAccountCommand.Register toCommand() {
            return StudentAccountCommand.Register.builder()
                    .name(name)
                    .studentNumber(studentNumber)
                    .email(email)
                    .phoneNum(phoneNum)
                    .registrationStatus(RegistrationStatus.from(registrationStatus))
                    .schoolYear(DthStringUtil.removeStr(schoolYear))
                    .courseInfoIdList(courseInfoIdList)
                    .build();
        }
    }
}

결론

먼저 처음 알게된 것은 List< 여기에도 애너테이션 사용이 가능 Type> 이라는 것이다.

또한 DTO로 했을 때는, Controller의 @RequestBody 객체에 붙인 @Valid로는 해당 클래스단 까지만 유효성검증이 이루어지는 걸까?

Controller의 request는

@Valid @RequestBody StudentAccountDTO.RegisterRequest request

정말 request 객체의 변수까지만 유효성 검증이 되었고 request 변수 중 songDtoList 처럼

private List<SongDTO> songDtoList;

특정 DTO 클래스 Type으로 받는다면 위에 @Valid를 꼭 써서 검증을 주입해주자.

0개의 댓글

관련 채용 정보