RequestDto에서 MultipartFile 필드 사용 방법: 객체 바인딩 방법부터 테스트 코드까지
📌 서론
기존에 RequestDto로 사용하던 dto에 MultipartFile 필드를 추가해줘야 할 일이 있었다. RequestDto에 MultipartFile 필드를 추가해 주면서 기존에 사용하던 방식과 다르게 사용해야 하는 부분들이 여러 개 있어서 그 점에 대해서 설명해보려고 한다.
특히, @RequestBody 대신 @ModelAttribute 어노테이션을 사용해야 하는 이유와 RequestDto에 Setter 메서드가 필요한 이유, 그리고 테스트 코드의 수정에 초점을 맞춰 설명하겠다.
1. 컨트롤러 어노테이션: @RequestBody에서 @ModelAttribute로 전환
기존에 컨트롤러에서 RequestDto를 선언할 때 @RequestBody 어노테이션을 사용했다면, MultipartFile 필드가 있는 RequestDto는 @ModelAttribte를 사용해야 한다.
@RequestBody
@RequestBody 어노테이션의 목적은 HTTP 요청의 본문(Body)에 있는 데이터(JSON, XML 등)를 Java 객체로 변환하는 것이다.
@RequestBody 어노테이션은 REST API에서 JSON 또는 XML 형식의 데이터를 처리하는 데 사용되고 HttpMessageConverter를 통해 요청 본문을 Java 객체로 자동 변환해 준다.
@ModelAttribute
@ModelAttribute 어노테이션의 목적은 HTTP 요청의 파라미터를 Java 객체의 필드에 바인딩하는 것이다. HTML 폼 데이터 처리, 특히 multipart/form-data 형식 (파일 업로드 포함)에서 사용된다.
@ModalAttribte는 요청 파라미터를 객체의 필드에 직접 바인딩한다.
MultipartFile이 있는 경우 @ModelAttribute 사용 이유
- @RequestBody는 요청 본문 전체를 한 번에 읽어서 처리하기 때문에, multipart/form-data 형식의 데이터를 적절히 처리할 수 없다. 이 형식은 파일과 일반 텍스트 데이터를 혼합해서 전송하는 방식이기 때문에, 복잡한 분석과 파싱 과정이 필요하다.
- @ModelAttribute는 multipart/form-data 형식을 자연스럽게 처리할 수 있다. HTML 폼에서 전송된 각 파라미터를 개별적으로 Java 객체의 필드와 매핑하기 때문에, 파일과 텍스트 데이터를 동시에 처리할 수 있다.
2. RequestDto: Setter 메서드 추가
@RequestBody의 경우, JSON 또는 XML 데이터를 객체에 매핑할 때, 리플렉션을 사용하여 직접 필드에 접근할 수 있기 때문에 getter/setter가 필수적이지 않다. 하지만 @ModelAttribute는 setter를 통한 데이터 바인딩을 기본으로 한다.
@ModelAttribute는 요청 파라미터를 객체 필드에 바인딩할 때, "Java Beans 규약"을 따르기 때문이다. 이 규약에 따르면, 프로퍼티를 설정하기 위해 setter 메서드가 필요하다.
예를 들어, 요청 파라미터에 email이라는 값이 있다면, @ModelAttribute는 setEmail(String email) 메서드를 찾아서 이 메소드를 통해 해당 값을 객체의 email 필드에 설정한다.
3. 컨트롤러 테스트코드 수정 - @RequestBody에서 @ModelAttribute로의 전환
컨트롤러에서 @RequestBody에서 @ModelAttribute로 어노테이션을 변경하면서, 테스트 코드도 이에 맞춰 변경해야 했다. 데이터의 처리 방식에 차이가 있기 때문이었다.
기존 @RequestBody 사용 시
@RequestBody는 JSON 형식의 데이터를 처리한다. 테스트 코드에서는 JSON 형태의 문자열 데이터를 사용한다.
다음 코드는 MockMvc를 통해 JSON 데이터를 포함한 POST 요청을 전송하는 내용이다.
@Test
void requestBodyTest() throws Exception {
RequestDto requestDto = new RequestDto(/* 필요한 데이터 */);
mockMvc.perform(post("/member/signUp")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(requestDto))) // JSON 데이터
.andExpect(status().isOk());
}
// JSON 문자열 변환을 위한 유틸리티 메서드
private String asJsonString(final Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
변경된 @ModelAttribute 사용 시
@ModelAttribute는 폼 데이터를 객체에 바인딩하는 데 사용된다. multipart/form-data 형식을 사용해 파일과 폼 데이터를 전송한다.
테스트 시 MediaType.MULTIPART_FORM_DATA를 사용하고, flashAttr을 통해 DTO 객체를 전달한다. 그리고 MultipartFile 데이터는 별도로 처리해야 한다.
@Test
void modelAttributeTestWithMultipart() throws Exception {
MockMultipartFile mockFile = new MockMultipartFile("file", "filename.txt", "text/plain", "some xml".getBytes());
SignUpRequestDto requestDto = new SignUpRequestDto(/* 필요한 데이터 */);
mockMvc.perform(MockMvcRequestBuilders.multipart("/member/signUp")
.file(mockFile)
.flashAttr("requestDto", requestDto)) // 폼 데이터
.andExpect(status().isOk());
}
이 코드는 실제 파일 업로드를 포함한 multipart/form-data 요청을 시뮬레이션하기 위해 MockMvcRequestBuilders.multipart() 메서드를 사용한다. MockMultipartFile을 사용해 파일 데이터를 포함시키고, flashAttr을 통해 SignUpRequestDto 객체를 전달한다. 파일 업로드를 포함한 보다 복잡한 시나리오에서 사용되는 방식이다.
flashAttr 메서드
flashAttr 메서드는MockMvc의 post 요청에서 폼 데이터를 전달하는 데 사용된다. 실제 HTML 폼에서 사용자가 데이터를 입력하고 제출하는 것과 유사한 방식으로, RequestDto 객체를 전달하는 데 사용된다.
☝️ 잠깐! flashAttr 메서드 사용 시 주의사항
flashAttr 메서드의 첫 번째 파라미터인 문자열은 컨트롤러 메서드에서 @ModelAttribute 어노테이션으로 받는 객체의 이름과 일치해야 한다.
예를 들어, 컨트롤러 메서드가 다음과 같이 선언되어 있다고 가정해 보자.
@PostMapping("/signUp")
public String signUp(@ModelAttribute("signUpRequest") SignUpRequestDto signUpRequestDto) {
// ...
}
여기서 @ModelAttribute 어노테이션에 명시된 "signUpRequest"가 바로 flashAttr의 첫 번째 파라미터로 전달되어야 하는 문자열이다. 따라서, 테스트 코드에서는 다음과 같이 작성해야 한다:
@Test
void modelAttributeTest() throws Exception {
SignUpRequestDto requestDto = new SignUpRequestDto(/* 필요한 데이터 */);
MockMultipartFile mockFile = new MockMultipartFile("file", "filename.txt", "text/plain", "some xml".getBytes());
mockMvc.perform(post("/member/signUp")
.contentType(MediaType.MULTIPART_FORM_DATA)
.flashAttr("signUpRequest", requestDto)) // 폼 데이터
.andExpect(status().isOk());
}
💡 만약에 @ModelAttribute 어노테이션을 사용할 때 명시적으로 이름을 지정하지 않았다면?
@ModelAttribute 어노테이션을 사용할 때, 괄호 안에 명시적으로 이름을 지정하지 않으면, 스프링은 자동으로 파라미터 타입의 이름을 사용한다. 이 이름은 타입의 첫 글자를 소문자로 변환한 형태다.
따라서, @ModelAttribute SignUpRequestDto signUpRequestDto와 같이 사용할 경우, 스프링은 자동으로 이 객체를 "signUpRequestDto"라는 이름으로 처리한다.
🔥 결론
이 글에서는 RequestDto에 MultipartFile 필드를 추가하면서 발생하는 주요 변경 사항들을 살펴보았다.
핵심적인 변화는 @RequestBody에서 @ModelAttribute로의 전환과 관련된 것이었다. @ModelAttribute는 multipart/form-data 형식의 데이터를 처리하는 데 필요하며, 이는 파일과 일반 폼 데이터를 함께 처리할 수 있다는 장점이 있다.
이와 함께, RequestDto에 Setter 메서드를 추가해야 하는 이유도 Java Beans 규약에 따라 데이터 바인딩이 이루어지기 때문이다.
또한, 컨트롤러의 테스트 코드 수정도 중요한 부분이었다. @RequestBody를 사용할 때는 JSON 데이터를 활용한 반면, @ModelAttribute 사용 시에는 MockMvc의 multipart() 메서드를 통해 폼 데이터와 MultipartFile을 전송하는 방식으로 변경되었다. flashAttr 메서드를 사용해 컨트롤러 메서드의 파라미터와 일치하는 이름으로 객체를 전달하는 방법도 설명했다.
이번 기회로 MultipartFile과 다양한 연관 관계에 있는 프로세스에 대해서 더 잘 이해하게 된 것 같아서 좋은 기회라고 생각한다.
📣 이 글은 내가 소속된 Team Chillwave에서 진행한 사이드 프로젝트에 적용한 내용을 다시 공부하고 정리한 것이다.
다른 팀원인 "개발자의 서랍" 님의 블로그도 방문하면 도움이 될 것 같다 :)
'Spring > Spring Boot' 카테고리의 다른 글
객체 간 매핑을 도와주는 MapStruct 라이브러리(1) - 기본 어노테이션 (0) | 2024.04.02 |
---|---|
Lombok의 @Builder 어노테이션으로 객체 생성 (0) | 2024.04.02 |
[Spring Boot] 스프링 부트 3에 레디스 적용하기 (1) | 2024.01.09 |
스프링 부트 3 버전에서 AWS SNS 클라이언트 여러 개 사용하기 (2) | 2023.12.29 |
Spring Boot 3 버전에서 AWS SNS를 통한 SMS 발송하기 (3) | 2023.12.29 |