ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] DTO의 모든 것
    Spring 2024. 6. 25. 13:51

    개발을 하면서 DTO에 대해서 깊이 알고 싶어 공부한 주제이다

     

    DTO란?

    • Data Transfer Object의 약자이다
    • REST API 작성 시에 엔티티 대신에 DTO를 사용하여 컨트롤러에서 데이터를 주고 받는 용도로 사용
    • DTO를 사용하면 엔티티에 변질을 막을 수 있다
    • 로직에 맞춰 필요한 필드만 주고 받을 수 있어 DTO를 사용해야 한다

     

     

    작자는 request, response DTO 처럼 따로 두고 설정한다

     

    Request DTO

    • REST API를 개발할 때 HTTP 요청이 들어오게 되고 그 Body 값을 가져올 때 DTO 맵핑을 해야 한다
    • 역직렬화가 진행된다
    • 역직렬화를 하기 위해선 Jackson이 동작하는데 Jackson 내부에서는 ObjectMapper가 리플랙션을 사용한다
    • SpringBoot 에서는 @Request Body를 맵핑한다

     

    DTO 역직렬화에선 어떤게 필요 할까?

    • 역직렬화할때 기본 생성자(default constructor)를 이용하여 객체를 생성한 후에 getter나 setter를 이용해서 필드를 가져온다
    • 필드를 가져오기 위해서 reflection을 이용한다
    • setter는 필드 값이 바뀔 가능성이 높여주기 때문에 getter를 선언하는 것 이 좋다

    예시)

    Dto

    import jakarta.validation.constraints.Min;
    import jakarta.validation.constraints.NotBlank;
    import jakarta.validation.constraints.NotNull;
    import jakarta.validation.constraints.Size;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @NoArgsConstructor
    @Getter
    public class registerRequest {
        @Min(1)
        @NotNull
        private Integer id;
    
        @NotBlank
        @Size(max = 20)
        private String name;
    }

     

    Controller

    @PostMapping("/api/test")
        public ResponseEntity<RegisterResponse> register(@Valid @RequestBody RegisterRequest registerRequest, 
        							BindingResult bindingResult) {
            if (bindingResult.hasErrors()) {
                throw new RequestValidationFailedException(bindingResult);
            }
            RegisterResponse response = service.register(registerRequest);
    
            return ResponseEntity
                    .status(HttpStatus.CREATED)
                    .body(response);
        }

     

    위와 같이 왜 @Setter를 사용하지 않아도 작동이 될까?

    Spring 에서 Json 데이터의 형 변환을 시켜주는 것은 Jackson2HttpMessageConverter를 사용한다

    그렇기 때문에 실제로 변환을 진행하는 것은 ObjectMapper 클래스의 함수인 readValue를 사용하기 때문에 Setter가 없더라도 정상적으로 수행된다

     

     

    만약 Controller에서 @RequestBody를 사용하지 않는다면?

     

    application/json 타입이 아닌 application/x-www-form-data 형태로 요청이 들어오게 된다

    위 코드에서 @RequestBody만 제거를 하게 된다면 Body의 값에 null 이 들어가게 된다

     

    왜?

    • @PostMapping과 같은 어노테이션이 붙은 함수의 파라미터는 @RequestParam이 붙어 있다고 보면되는데
    • @RequestParam이 붙은 인자의 타입이 String이 아닐 경우에 Type Conversion이 자동으로 적용된다
    • 하지만 Type Conversion이 타입을 변환하는 방법이 DataBinder이고
    • DataBinderJava Bean Spec을 따라서 Setter가 필요하다

    Java Bean Spec

    1. 기본 생성자를 가지고 있어야 한다.
    2. 인스턴스 변수는 private 접근 지정자여야 한다.
    3. public의 getter와 setter를 가지고 있어야 한다.

     

    그럼 테스트 코드에서 RequestDto를 어떻게 생성하고 test를 해야할까?

     

    테스트 코드 작성시 Request DTO를 직접 만들어주고 필드에 값을 주입해야 하는데 기본생성자와 getter 밖에 없어 이걸 어떻게 하지 라는 생각을 했다

     

    방법은 

    Reflection를 이용하는거다

    util 에 있는 ReflectionTestUtils 클래스를 이용하면 된다

     

    기본생성자로 객체를 만든 후,

    ReflectionTestUtils에 setField()로 필드에 값을 주입하면 된다

    import jakarta.validation.constraints.Min;
    import jakarta.validation.constraints.NotBlank;
    import jakarta.validation.constraints.NotNull;
    import jakarta.validation.constraints.Size;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @NoArgsConstructor
    @Getter
    public class registerRequest {
        @Min(1)
        @NotNull
        private Integer id;
    
        @NotBlank
        @Size(max = 20)
        private String name;
    }
    @Test
    void registerTest(){
    	RegisterRequest registerRequest = new RegisterRequest();
    
    	ReflectionTestUtils.setField(registerRequest, "name", "jaehyeon");
        ...
    }

     

    setFiled(객체 변수 이름, filed 변수 이름, 값);

     

     

    Response DTO

    • HTTP 요청이 들어오고  해당 요청을 처리한 뒤 응답을 보낼때 엔티티를 DTO로 변환 해서 보내야 한다
    • 직렬화가 진행된다

     

    DTO 직렬화에선 어떤게 필요 할까?

     

    responseDTO에서는 getter가 있어야만 오류가 나지 않았다

    import lombok.Getter;
    import lombok.AllArgsConstructor;
    
    @AllArgsConstructor
    @Getter
    public class registerResponse {
        private Integer id;
        private String name;
    }

     

    'Spring' 카테고리의 다른 글

    REST API 에서의 HTTP 상태 코드 (HTTP Status Code)  (0) 2024.06.20
Designed by Tistory.