File Upload์ ํ์ํ Request Annotation(@RequestParam, @RequestPart, @ModelAttribute, @RequestBody)
ํ์ต ๋ฐฐ๊ฒฝ
ํ๋ก์ ํธ์ ์ ์ฉ๋ ํ์ผ ์ ๋ก๋ ๋ฐฉ์์ด 2๊ฐ์ง์ธ ๊ฒ์ ํ์ธํ๊ณ ์ด๋ค ๊ฒ์ผ๋ก ํต์ผํ ์ง ๊ฒฐ์ ํ๊ธฐ ์ํด์ ๊ณต๋ถํ์์
File Upload
@RequestParam
@RequestParam์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ file ๋ฐ์ดํฐ ์ธ์ json ํ์์ ๋ฐ์ดํฐ๋ ๋ฐ์ ์ ์๋ค.
single file ์์
1 @PostMapping("/request-param/single") 2 public String singleRequest(@RequestParam MultipartFile file){ 3 return "request param single "+file.getOriginalFilename(); 4 }
multi file & other request param ์์
- ์ฌ๋ฌ๊ฐ์ multi file paramter ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉฐ
List<MultipartFile>๋ ์ฌ์ฉ ํ ์ ์๋ค. - ๋ค๋ง RequestParam์ด ๋๋ฌด ๋ง์ผ๋ฉด ์ ์ง๋ณด์๊ฐ ํ๋ค์ด์ง๊ธฐ ๋๋ฌธ์ ์ ์ ํ๋ผ๋ฏธํฐ์ผ ๋๋ ์ฌ์ฉ์ ๊ณ ๋ คํด ๋ณผ ์ ์๊ฒ ๋ค
1 @PostMapping("/multi-with-other-params") 2 public String withOtherParamsRequest(@RequestParam MultipartFile file1, @RequestParam MultipartFile file2, 3 @RequestParam String name, @RequestParam String desc){ 4 return "request param with other params "+file1.getOriginalFilename()+"_"+file2.getOriginalFilename()+ 5 "_"+name+"_"+desc; 6 }
@RequestPart
@RequestPart๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ file ๋ฐ์ดํฐ ์ธ์ @RequestPart๋ฅผ ์ฌ์ฉํ์ฌ json ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋ค.
single file & json ์์
- backend code
1 @PostMapping("/with-request-part") 2 public String withRequestPart(@RequestPart MultipartFile file, @RequestPart RequestPartTestBody requestBody){ 3 return "request part with request part "+file.getOriginalFilename()+"_"+requestBody.toString(); 4 } 5 6 @Getter 7 @ToString 8 public class RequestPartTestBody { 9 private String name; 10 private String desc; 11 }
- frontend code
json ํ์ผ์ ๋๊ธธ ๋new Blob()๊ฐ์ฒด๋ก ๋ณํํ๊ณ{type: "application/json"}์ผ๋ก ContentType์ ์ง์ ํด์ค์ผ ์ ์์ ์ผ๋ก ๋์ํ๋ค.
1async function submitRequestPartWithRequestPart() { 2 const bodyData = new FormData(); 3 const file = document.querySelector("#file").files; 4 bodyData.append("file", file[0]); 5 bodyData.append( 6 "requestBody", 7 new Blob( 8 [ 9 `{"name": "${document.querySelector("#name").value}", "desc": "${ 10 document.querySelector("#desc").value 11 }"}`, 12 ], 13 { type: "application/json" } 14 ) 15 ); 16 17 await post(bodyData, "request-part/with-request-part", "request-part-with-request-part"); 18} 19 20async function post(bodyData, uri, preId) { 21 const context = "[[@{/}]]"; 22 await fetch(context + uri, { 23 method: "POST", 24 body: bodyData, 25 }) 26 .then((res) => { 27 if (res.ok) { 28 return res.text(); 29 } else { 30 return res.json(); 31 } 32 }) 33 .then((res) => { 34 if (typeof res == "object") { 35 document.querySelector(`#${preId}`).innerHTML = res.error; 36 document.querySelector(`#${preId}`).className = "error"; 37 } else { 38 document.querySelector(`#${preId}`).innerHTML = res; 39 document.querySelector(`#${preId}`).className = "result"; 40 } 41 }); 42}
@ModelAttribute
@ModelAttribute๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ file ํ์
ํ๋์ ๊ธฐํ json์ผ๋ก ๋ฐ๊ณ ์ถ์ ๋ฐ์ดํฐ๋ค์ RequestDto๋ก ํ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ค.
RequestDto ์์
1 @PostMapping(value = "in-the-request-body", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) 2 public String inTheRequestBody(@ModelAttribute ModelAttributeTest2Body requestBody){ 3 return "model attribute in the request body "+requestBody.toString(); 4 } 5 6 @Getter @Setter 7 public class ModelAttributeTest2Body { 8 private String name; 9 private String desc; 10 private MultipartFile file; 11 12 public String toString(){ 13 return "{"+ 14 "\"name\":\""+name+"\","+ 15 "\"desc\":\""+desc+"\","+ 16 "\"file\":\""+file.getOriginalFilename()+"\"}"; 17 } 18 }
1async function submitModelAttributeInTheRequestBody() { 2 const bodyData = new FormData(); 3 const file = document.querySelector("#file").files; 4 bodyData.append("file", file[0]); 5 bodyData.append("name", document.querySelector("#name").value); 6 bodyData.append("desc", document.querySelector("#desc").value); 7 8 await post(bodyData, "model-attribute/in-the-request-body", "model-attribute-in-the-request-body"); 9}
์ ์ฒด ์ฝ๋
๊ฒฐ๋ก
๋ ์ค ํ๋๋ก ํต์ผํ๊ธฐ ์ํด์ ์คํฐ๋๋ฅผ ํ์ผ๋ ๊ฒฐ๊ณผ์ ์ผ๋ก ์ํฉ์ ๋ฐ๋ผ ์ฌ์ฉํ ์ ์์์ ํ์ธํ๋ค.
@RequestPart์ ๊ฒฝ์ฐ file๋ถ/json๋ถ๋ก ๋๋ ์ผ ํ๊ณ front์์ json์ ์์ฑํ์ฌ Blob ๊ฐ์ฒด๋ก ๋ณํํ๋ ์์
๊น์ง ์ด๋ฃจ์ด์ ธ์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ฒ๊ฑฐ๋ก์ธ ์ ์๋ค. @ModelAttribute์ ๊ฒฝ์ฐ์๋ front์์ FormData์ ๊ฐ๊ฐ appendํ์ฌ๋ requestBody์ ์๋์ผ๋ก(์ ํํ๋ ๋ณ์๋ช
์ผ๋ก ์ถ์ธก๋๋ setter๋ฅผ ์ฌ์ฉํด์) ๋งคํ๋๋ค.
ํ์ง๋ง ๋ด๊ฐ ๋ด๋นํ๊ณ ์๋ ํ๋ก๊ทธ๋จ์ ๊ฒฝ์ฐ request๋ก ๋ฐ๋ json ๊ตฌ์กฐ๊ฐ ๊ต์ฅํ ๋ณต์กํ๊ธฐ ๋๋ฌธ์ ํ
์คํธ ์ธก๋ฉด์์๋ @RequestPart๋ฅผ ์ ์งํ๋ ๊ฒ์ด ์ข์๋ณด์ธ๋ค.
์ถ๊ฐ๋ก ๊ฐ ์ด๋ ธํ ์ด์ ์ ํน์ง๊ณผ RequestDto๋ก ๋งคํํ๋ ๊ณผ์ ์ ์ ๋ฆฌํ๊ณ ์ ํ๋ค.
Annotation๋ณ ํน์ง, ๋ฐ์ดํฐ ๋งคํ ๋ฐฉ๋ฒ
@RequestParam
๊ฐ๋จํ name-value ํํ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋ ์ฌ์ฉํ ์ ์๋ค. @RequestPart์ ๋ง์ฐฌ๊ฐ์ง๋ก MultipartFile์ ๋ฐ์ ๋๋ ์ฌ์ฉํ ์ ์๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก required ์์ฑ์ด true์ด๊ธฐ ๋๋ฌธ์ optional ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ required=false ์ค์ ์ ํด์ค์ผ ํ๋ค.
@RequestBody
HTTP ์์ฒญ์ผ๋ก ๋์ด์ค๋ body ๋ด์ฉ์ HttpMessageConverter๋ฅผ ํตํด ์ญ์ง๋ ฌํํ๋ค. json ๊ธฐ๋ฐ์ ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ๋ ์์ฒญ์ธ ๊ฒฝ์ฐ ์ ์ฉ๋ ์ ์์ผ๋ฉฐ Multipart์์ฒญ์ด ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ ์ญํ ์ ํ๋ค.
- HttpMessageConverter
json ๋ฐ์ดํฐ๋ฅผ ์ญ์ง๋ ฌํ๋ฅผ ํตํด ๊ฐ์ฒด๋ก ๋ง๋๋ ์ฃผ์ฒด.
RequestMappingHandlerAdapter๊ฐ ๋ฐ์ดํฐ์ MIME ํ์ ์ ํ์ธํ ํapplication/json์ธ ๊ฒฝ์ฐ์๋MappingJackson2HttpMessageConverter๋ฅผ ์ฌ์ฉํ๋๋ก ์ ์ ํ๋ค.
MappingJackson2HttpMessageConverter๋ ๋ด๋ถ์ ์ผ๋กObjectMapper๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ค.(์ถ์ฒlink)
ObjectMapper๋ ๊ธฐ๋ณธ ์์ฑ์์ @Getter๋ง ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ณํํ ์ ์๊ธฐ ๋๋ฌธ์ RequestDto์ @Setter annotation์ ๋ถํ์ํ๋ค.
(์ ํํ๋ @Getter, @Setter ๋ ์ค ํ๋๋ง ์์ด๋ ๋์ง๋ง ๊ฐ์ฒด ์์ฑ ํ ๋ณ์์ ์ ๊ทผํ๊ธฐ ์ํด @Getter๋ฅผ ์ฌ์ฉ)(์ถ์ฒlink)
@RequestPart
MultipartFile์ด ํฌํจ๋๋ ๊ฒฝ์ฐ MultipartResolver๊ฐ ๋์ํ์ฌ ์ญ์ง๋ ฌํํ๋ค. ๋ฐ์ดํฐ์ MultipartFile์ด ํฌํจ๋์ง ์๋ ๊ฒฝ์ฐ @RequestBody์ ๊ฐ์ด ๋์ํ๊ฒ ๋๋ค.
๋ฐ๋ผ์ @RequestBody๊ฐ ํ์ํ์ง๋ง ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋๋ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์๋ค.
@ModelAttribute
Spring Controller์์ ๊ฐ์ ๋ฐ์ ๋ default ๋ก ์ ์ฉ๋๋ ์ด๋
ธํ
์ด์
์ด๋ค. request body, request parameter ๋ชจ๋๋ฅผ ๋ฐ์ ์ ์๊ณ MultipartFile ์ด ํฌํจ๋์ด ์์ ๋๋ ์ฒ๋ฆฌํ ์ ์๋ค.
๋ค๋ง @RequestBody, @RequestPart์ ๋ฌ๋ฆฌ ๊ธฐ๋ณธ์์ฑ์+@Setter ๋๋ @AllArgsConstructor๊ฐ ์์ด์ผ RequestDto์ ๋ฐ์ธ๋ฉํ ์ ์๋ค.(์ถ์ฒlink)