JustDoEat

[SpringMVC,문제해결] 멀티파트 요청에서 File 타입, DTO 바인딩 문제 ModelAttribute와 RequestPart 차이에서 오는 문제 본문

Yajoba/Backend

[SpringMVC,문제해결] 멀티파트 요청에서 File 타입, DTO 바인딩 문제 ModelAttribute와 RequestPart 차이에서 오는 문제

kingmusung 2024. 11. 30. 22:25

개요

multipart 요청

 

이미지 파일과 Json형식의 데이터를 같이 보내야 하는 상황에서.

어떤 느낌인지

 

MultipartFile 타입의 인자를 클라이언트로 받음과 동시에 컨트롤러에서 DTO에 한 번에 맵핑시키고 싶었다.

 

하지만 일반적인 DTO 맵핑과 다르게 맵핑이 되지 않아 골머리를 썩고 있었다.

 

 

 


문제 해결을 위해 각 특성을 살펴보고 해결해 보겠다.

RequestBody, RequestParam, RequestPart

 

RequestBody

Content-Type = application/json

 

Content-Type이 application/json 인 데이터를 받을 때 우리가 통상적으로 많이 쓰는 어노테이션이다.

 

HTTP 요청의 본문의 데이터를 DTO의 필드 값과 맵핑을 시켜줄 때 사용한다.

 

포스트맨 요청 예시

 

@Getter
public class ReviewCreateReqDto{
	private final Long productId;
    private final int rating;
    private final String content;
    
    ReviewCreateReqDto(Long productId, int rating, String content){
    this.productId = productId;
    this.rating = rating;
    this.content = content;
    }
    
}

 

해당 요청 본문에 있는 값들은 컨트롤러로 들어올 때 @RequestBody라는 어노테이션에 의해 자동으로 맵핑이 된다.

@GetMapping("/reviews")
    public String getProduct(@RequestBody ReviewCreateReqDto reviewCreateReqDto)

 

하지만, 나의 상황은 

Content_type = multipart/form-data

Content_type = multipart/form-data 이기 때문에 배제하도록 하겠다.


RequestParam

@GetMapping("/products")
    public String getProduct(@RequestParam String name, @RequestParam int price)

 

RequestParam은 

GET /products?name=laptop&price=1200

 

위처럼 쿼리 파라미터를 맵핑하는 데 사용한다.

 

혹은 Content_type = multipart/form-data에서 application/json 타입의

간단한 데이터를 맵핑할 때 사용되고, DTO로의 변환은 허용하지 않는다.

 

Content_type = multipart/form-data 의 간단한 예시

 

body부분에 application/json 이 있다고 Header에 Content_type이 application/json 인건 절대 아니다.

 

Header에 Content_type은 Content_type = multipart/form-data이다.

 

requestParam

 

성공적으로 잘 된다.

 

 

DTO로의 변환을 시도한다면?

RequestParam dto변환시 오류

'java.lang.String' to required type 
'com.gulbi.Backend.domain.rental.product.dto.product.request.ProductRegisterRequestDto';
Cannot convert value of type 'java.lang.String' to required type 'com.gulbi.Backend.domain.rental.product.dto.product.request.ProductRegisterRequestDto' for property 'productInfo'

 

dto로

 

일단 DTO로의 맵핑이 안되므로 RequestParam도 탈락이다.

 

+

 

@PathVariable

@GetMapping("/products/{id}")
public String getProduct(@PathVariable Long id

 

RequestPart

 

multipart 요청

 

멀티파트 요청에서 파일과 Json 데이터를 같이 보낼 때 사용한다,

 

Json만 보낼 때도 사용가능하다 하지만 이럴 거면 멀티파트 요청을 할 필요는 없겠다.

 

추가로 RequestParam과 다르게 DTO로의 변환을 허용한다.

 

RequestPart로 요청받기


"나 : 그러면 RequestPart를 사용하면 문제 해결이네??
??? : 아니."

 

나는 json타입을 못 받아서 고생을 한 게 아니다, 멀티파트에서 파일 부분을 받아올 때 MultipartFile 타입이 아닌

 

 

깔삼하게 DTO로 변환을 받고 싶었다.

 

 

문제는 이렇게 요청을 보내면 내가 의도한 방식대로 동작하지 않고, ProductImageCollection에는 null값이 담기게 된다,

 

즉 맵핑이 제대로 되지 않았던 거다.


"ModelAttribute를 사용하면 해결이 된다."

 

ModelAttribute

 

이렇게 되면 복잡한 DTO 생성로직에도 굴복하지 않고 바로 맵핑이 가능하다.

 

Modelattribute의 정의를 찾아보면, ContentType = multipart-formdata에서 사용을 하고, 폼데이터로 들어온 데이터를 dto에 바인딩을 시켜준다는데 RequestPart 차이점을 잘 모르겠다.

 

하지만 한 가지 알아낸 건 있다.


RequestPart와 ModelAttribute의 객체생성 차이.

최종 컨트롤러

 

ModelAttribute는 생성자를 이용
RequestPart는 생성자를 이용하지 않음

 

해당 실험에서 볼 수 있는 지표는, ModelAttribute는 생성자를 이용, RequestPart는 생성자를 이용하지 않음.이다.

 

요청값에서 단순히 String, Long, int, double 등 기본적인 자료형의 값을 추출해 DTO와 매핑은 RequestPart가 가능하지만.

 

나의 경우처럼 DTO 타입에 기본적인 자료형이 아닌, 다른 타입(클래스, 엔티티, 일급 컬렉션 등)을 받는다면 스프링이 인식을 할 수 없다.

 

객체 생성 시 필요한 로직을 생성자에 정의를 했지만 RequestPart는 생성자를 이용해서 DTO를 만들지 않기 때문에 맵핑이 되지 않지만.

 

ModelAttribute는 생성자를 이용하기 때문에 맵핑이 된 것이다.