File Upload์— ํ•„์š”ํ•œ Request Annotation(@RequestParam, @RequestPart, @ModelAttribute, @RequestBody)

2024-11-27

ํ•™์Šต ๋ฐฐ๊ฒฝ

ํ”„๋กœ์ ํŠธ์— ์ ์šฉ๋œ ํŒŒ์ผ ์—…๋กœ๋“œ ๋ฐฉ์‹์ด 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}

์ „์ฒด ์ฝ”๋“œ

request-annotation-test

๊ฒฐ๋ก 

๋‘˜ ์ค‘ ํ•˜๋‚˜๋กœ ํ†ต์ผํ•˜๊ธฐ ์œ„ํ•ด์„œ ์Šคํ„ฐ๋””๋ฅผ ํ–ˆ์œผ๋‚˜ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ์„ ํ™•์ธํ–ˆ๋‹ค.
@RequestPart์˜ ๊ฒฝ์šฐ file๋ถ€/json๋ถ€๋กœ ๋‚˜๋ˆ ์•ผ ํ•˜๊ณ  front์—์„œ json์„ ์ƒ์„ฑํ•˜์—ฌ Blob ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…๊นŒ์ง€ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ๋‹ค. @ModelAttribute์˜ ๊ฒฝ์šฐ์—๋Š” front์—์„œ FormData์— ๊ฐ๊ฐ appendํ•˜์—ฌ๋„ requestBody์— ์ž๋™์œผ๋กœ(์ •ํ™•ํžˆ๋Š” ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์ถ”์ธก๋˜๋Š” setter๋ฅผ ์‚ฌ์šฉํ•ด์„œ) ๋งคํ•‘๋œ๋‹ค.
ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ๋Š” ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ์šฐ request๋กœ ๋ฐ›๋Š” json ๊ตฌ์กฐ๊ฐ€ ๊ต‰์žฅํžˆ ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ธก๋ฉด์—์„œ๋Š” @RequestPart๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„๋ณด์ธ๋‹ค.

์ถ”๊ฐ€๋กœ ๊ฐ ์–ด๋…ธํ…Œ์ด์…˜์˜ ํŠน์ง•๊ณผ RequestDto๋กœ ๋งคํ•‘ํ•˜๋Š” ๊ณผ์ •์„ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.

Annotation๋ณ„ ํŠน์ง•, ๋ฐ์ดํ„ฐ ๋งคํ•‘ ๋ฐฉ๋ฒ•

์ถœ์ฒ˜link

@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)

/end of File Upload์— ํ•„์š”ํ•œ Request Annotation(@RequestParam, @RequestPart, @ModelAttribute, @RequestBody)
CONTENT LISTMERRI๏ผ‡s DEVELOG
Spring Security - OAuth2.0 ์‚ฌ์šฉ ์‹œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋ฅผ ์ง€์›ํ•˜๊ธฐ
2024-12-10