๐ 1. MultipartFile์ด๋?
MultipartFile์ Spring์ด ์ ๊ณตํ๋ ํ์ผ ์
๋ก๋๋ฅผ ์ํ ๋ด์ฅ ์ธํฐํ์ด์ค.
multipart/form-data ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๊ณ , ํ์ผ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ์ ์๋ค.
โ 1-1. MultipartFile ์ฃผ์ ํน์ง
- ์คํ๋ง์ด ์๋์ผ๋ก ํ์ผ์ ๋ฐ์ธ๋ฉํด ์ค.
- ๋จ์ผ ํ์ผ & ๋ค์ค ํ์ผ ์ ๋ก๋ ๊ฐ๋ฅ
- @ModelAttribute, @RequestParam๊ณผ ํจ๊ป ์ฌ์ฉ
- ํ์ผ ์ด๋ฆ, ํฌ๊ธฐ, ํ์ฅ์, ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ ์ ๊ทผ ๊ฐ๋ฅ
โ 1-2. ์ฃผ์ ๋ฉ์๋
| ๋ฉ์๋ | ์ค๋ช |
| getOriginalFilename() | ์ ๋ก๋๋ ํ์ผ์ ์๋ณธ ์ด๋ฆ ๊ฐ์ ธ์ค๊ธฐ |
| getSize() | ํ์ผ ํฌ๊ธฐ (๋ฐ์ดํธ ๋จ์) |
| getContentType() | ํ์ผ์ MIME ํ์ ํ์ธ |
| getBytes() | ํ์ผ์ byte ๋ฐฐ์ด๋ก ๋ณํ |
| getInputStream() | ํ์ผ์ InputStream์ผ๋ก ๊ฐ์ ธ์ค๊ธฐ |
| transferTo(File) | ์ง์ ๋ ์์น์ ํ์ผ ์ ์ฅ (ํ์ผ ์ ์ฅ ๊ฐ๋ฅ) |
โ 1-3. MultipartFile ์์
1๏ธโฃ ๋จ์ผ ํ์ผ ์ ๋ก๋
์์ฒญ ์์ (form-data): file: example.jpg
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("ํ์ผ์ ์ ํํด์ฃผ์ธ์.");
}
String fileName = file.getOriginalFilename();
long fileSize = file.getSize();
return ResponseEntity.ok("ํ์ผ ์
๋ก๋ ์ฑ๊ณต! ํ์ผ๋ช
: " + fileName + ", ํฌ๊ธฐ: " + fileSize + " bytes");
}
2๏ธโฃ ์ฌ๋ฌ ๊ฐ์ ํ์ผ ์ ๋ก๋
์์ฒญ ์์ (form-data): files: file1.jpg, file2.png, file3.pdf
@PostMapping("/uploadMultiple")
public ResponseEntity<String> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
System.out.println("ํ์ผ๋ช
: " + file.getOriginalFilename() + " | ํฌ๊ธฐ: " + file.getSize());
}
}
return ResponseEntity.ok("ํ์ผ ์
๋ก๋ ์ฑ๊ณต!");
}
3๏ธโฃ ํ์ผ ์ ์ฅํ๊ธฐ (transferTo ์ฌ์ฉ)
@PostMapping("/saveFile")
public ResponseEntity<String> saveFile(@RequestParam("file") MultipartFile file) throws IOException {
String savePath = "C:/upload/" + file.getOriginalFilename();
file.transferTo(new File(savePath)); // ํ์ผ ์ ์ฅ
return ResponseEntity.ok("ํ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋จ: " + savePath);
}
๐ 2. Spring์์ ํ์ผ + ๋ฐ์ดํฐ ์ ์ก ๋ฐฉ๋ฒ
๐จ @RequestBody๋ application/json์ ์ฌ์ฉํด์ผ ํ์ง๋ง,
๐จ ํ์ผ ์
๋ก๋๋ multipart/form-data ๋ฐฉ์์ ์ฌ์ฉํด์ผ ํด์ ๋์์ ์ฌ์ฉํ ์ ์๋ค!
๐ฅ JSON๊ณผ ํ์ผ์ ๊ฐ์ด ๋ณด๋ด๋ ค๋ฉด → @RequestPart ๋๋ @ModelAttribute ์ฌ์ฉ!
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="username">
<%-- multiple ์์ฑ์ผ๋ก ๋ค์ค ํ์ผ ์ ํ ๊ฐ๋ฅ, ํ์ฉํ์ฅ์๋ ๋ฐฑ์๋์์๋ ๊ด๋ฆฌ --%>
<input type="file" id="attachFiles" name="attachFiles" accept=".jpg, .jpeg, .png, .gif, .pdf, .zip, .doc, .docx, .xls, .xlsx, .txt" multiple>
<button type="submit">Upload</button>
</form>
โ 2-1. @ModelAttribute ์ฌ์ฉ (ํผ ๋ฐ์ดํฐ ๋ฐฉ์)
@ModelAttribute๋ ํผ ๋ฐ์ดํฐ ํ์ (multipart/form-data) ์ ์ด์ฉํด์ ๊ฐ์ฒด๋ฅผ ์๋ ๋งคํํ๋ ๋ฐฉ์์ด๋ค.
โ ํผ ํ๋ (title, content ๋ฑ)์ ํ์ผ์ ํจ๊ป ๋ณด๋ด์ผ ํ ๋ ์ฌ์ฉ
โ Postman์์ Form-Data ํ์์ผ๋ก ์์ฒญํด์ผ ํจ
๐น ๋ฐฑ์๋ ์ฝ๋ (Spring Controller)
@PostMapping("/insert")
public ResponseEntity<Note> insertNote(
@ModelAttribute Note note,
@RequestParam(value = "attachFiles", required = false) MultipartFile[] attachFiles) {
Note insertedNote = noteService.insertNote(note, attachFiles);
return new ResponseEntity<>(insertedNote, HttpStatus.OK);
}
โ @ModelAttribute Note note → ํผ ๋ฐ์ดํฐ๋ฅผ Note ๊ฐ์ฒด๋ก ์๋ ๋งคํ
โ @RequestParam MultipartFile[] attachFiles → ํ์ผ์ ๋ฐ๋ก ์ฒ๋ฆฌ
๐น ํ๋ก ํธ์๋ (JavaScript - FormData ์ฌ์ฉ)
import axios from "axios";
const handleSubmit = async () => {
const formData = new FormData();
formData.append("noteTitl", "ํ
์คํธ ์ ๋ชฉ");
formData.append("regtId", 1);
formData.append("noteCont", "๋ด์ฉ์
๋๋ค.");
// ํ์ผ ์ถ๊ฐ
const fileInput = document.getElementById("fileUpload");
if (fileInput.files.length > 0) {
formData.append("attachFiles", fileInput.files[0]);
}
try {
const response = await axios.post("http://localhost:8080/insert", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
console.log("์๋ต:", response.data);
} catch (error) {
console.error("์๋ฌ ๋ฐ์:", error);
}
};
๐น Postman ์์ฒญ ์ค์ (Form-Data ๋ฐฉ์)
- Method: POST
- Headers: Content-Type: multipart/form-data
- Body → form-data ์ ํ
- Key ๊ฐ ์ ๋ ฅ (name์ ํ๋๋ช ๊ณผ ๋์ผํด์ผ ํจ)
| Key | Value | Type |
| noteTitl | test1 | Text |
| regtId | 1 | Text |
| noteCont | ๋ด์ฉ ์ ๋ ฅ | Text |
| attachFiles | (ํ์ผ ์ ํ) | File |
โ 2-2. @RequestPart ์ฌ์ฉ (JSON + ํ์ผ ์ ๋ก๋)
โ JSON ๋ฐ์ดํฐ์ ํ์ผ์ ํจ๊ป ์ ์กํด์ผ ํ ๋ ์ฌ์ฉ
โ Postman ๋๋ ํ๋ก ํธ์์ FormData๋ฅผ ์ฌ์ฉํด์ JSON + ํ์ผ์ ๊ฐ์ด ์ ์ก
โ @RequestBody ๋์ @RequestPart ์ฌ์ฉํด์ผ ํจ (JSON์ multipart/form-data๋ก ์ฒ๋ฆฌ)
๐น ๋ฐฑ์๋ ์ฝ๋ (Spring Controller)
@PostMapping(value = "/insert", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
public ResponseEntity<Note> insertNote(
@RequestPart("note") Note note, // JSON ๋ฐ์ดํฐ ๋ฐ์
@RequestPart(value = "attachFiles", required = false) MultipartFile[] attachFiles) {
Note insertedNote = noteService.insertNote(note, attachFiles);
return new ResponseEntity<>(insertedNote, HttpStatus.OK);
}
๐น ํ๋ก ํธ์๋
import axios from "axios";
const handleUpload = async () => {
const formData = new FormData();
// JSON ๋ฐ์ดํฐ๋ฅผ Blob์ผ๋ก ๋ณํํ์ฌ ์ถ๊ฐ
const noteData = {
"noteTitl": "test1",
"regtId": 1,
"noteCont": "123",
"recipients": [ { "recvUserId": "user1", "recvCcGbn": "CC" }, { "recvUserId": "user2", "recvCcGbn": "BCC" } ]
};
const noteBlob = new Blob([JSON.stringify(noteData)], { type: "application/json" });
formData.append("note", noteBlob);
// ํ์ผ ์ถ๊ฐ
const fileInput = document.getElementById("fileUpload");
if (fileInput.files.length > 0) {
formData.append("attachFiles", fileInput.files[0]);
}
try {
const response = await axios.post("/insert", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
});
console.log("์
๋ก๋ ์ฑ๊ณต:", response.data);
} catch (error) {
console.error("์
๋ก๋ ์คํจ:", error);
}
};
โ JSON ๋ฐ์ดํฐ๋ฅผ Blob์ผ๋ก ๋ณํ ํ FormData์ ์ถ๊ฐํด์ผ JSON + ํ์ผ์ ๊ฐ์ด ๋ณด๋ผ ์ ์๋ค!
โ Content-Type์ ๋ฐ๋ก ์ง์ ํ์ง ์์๋ ์๋์ผ๋ก multipart/form-data๋ก ์ฒ๋ฆฌ๋จ
๐3. ์ ๋ฆฌ (์ด๋ค ๋ฐฉ์์ด ์ ์ ํ ๊น?)
| ๋ฐฉ์ | @RequestBody | @ModelAttribute | @RequestPart |
| JSON ๋ฐ์ดํฐ๋ง | โ ๊ฐ๋ฅ | โ ์๋จ | โ ๊ฐ๋ฅ |
| ํผ ๋ฐ์ดํฐ๋ง | โ ์๋จ | โ ๊ฐ๋ฅ | โ ๊ฐ๋ฅ |
| JSON + ํ์ผ ์ ๋ก๋ | โ ์๋จ | โ ์๋จ | โ ๊ฐ๋ฅ |
| ํผ ๋ฐ์ดํฐ + ํ์ผ ์ ๋ก๋ | โ ์๋จ | โ ๊ฐ๋ฅ | โ ๊ฐ๋ฅ |
โ @ModelAttribute → Form-Data ๋ฐฉ์์ผ๋ก ํผ ๋ฐ์ดํฐ + ํ์ผ์ ์ฒ๋ฆฌ
โ @RequestPart → JSON + ํ์ผ ์
๋ก๋๋ฅผ ์ฒ๋ฆฌ (ํ๋ก ํธ์์ FormData ์ฌ์ฉ)
โ @RequestBody + @RequestParam(MultipartFile) → โ ํจ๊ป ์ฌ์ฉํ ์ ์์
๐ฏ4. ๊ฒฐ๋ก & ์ถ์ฒ
1๏ธโฃ ํผ ๋ฐ์ดํฐ ๋ฐฉ์ (title, content์ ํ์ผ์ ๊ฐ์ด ๋ณด๋ผ ๋)
@ModelAttribute ์ฌ์ฉ (ํผ ๋ฐ์ดํฐ ์๋ ๋งคํ)
2๏ธโฃ JSON + ํ์ผ์ ๊ฐ์ด ๋ณด๋ผ ๋
@RequestPart ์ฌ์ฉ (ํ๋ก ํธ์์ FormData๋ก JSON + ํ์ผ ์ ์ก)
3๏ธโฃ ํ์ผ ์
๋ก๋ ๋จ๋
์ผ๋ก ๋ณด๋ผ ๋
@RequestParam MultipartFile ์ฌ์ฉ
๐ฅ ํ์ผ + JSON์ ๊ฐ์ด ๋ณด๋ด์ผ ํ๋ค๋ฉด? @RequestPart ์ถ์ฒ! ๐
๐ 5. ํ์ผ ๋ค์ด๋ก๋ ์์
@Value("${atchfile.upload.path}")
private String ATCHFILE_UPLOAD_PATH;
// ์ฒจ๋ถ ํ์ผ ๋ค์ด๋ก๋
@PostMapping("/download/attachFile.json")
public ResponseEntity<Resource> getAttachFile(@RequestBody NoteAttachRequestDTO request,
@RequestHeader(value = "X-User-Name", required = false) String username) {
try {
if(!noteRestService.checkNoteAuth(request.getSno(), username)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
NoteAttach file = noteAttachService.getAttachFile(request);
if(file == null){
return ResponseEntity.notFound().build();
}
Path filePath = Paths.get(ATCHFILE_UPLOAD_PATH + "/" + file.getAtchSaveNm()).toAbsolutePath().normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
String originalFileName = file.getAtchNm();
String encodedFileName = URLEncoder.encode(originalFileName, "UTF-8").replaceAll("\\+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedFileName + "\"")
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// ์ฒจ๋ถ ํ์ผ ์ ์ฒด ๋ค์ด๋ก๋
@PostMapping("/download/all/attachFile.json")
public ResponseEntity<Resource> getAllAttachFile(@RequestBody NoteAttachRequestDTO request,
@RequestHeader(value = "X-User-Name", required = false) String username) {
try {
if(!noteRestService.checkNoteAuth(request.getSno(), username)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
List<NoteAttach> atchList = noteAttachService.getAllAttachFile(request.getSno());
if (atchList == null || atchList.isEmpty()) {
return ResponseEntity.notFound().build();
}
Path zipFilePath = Files.createTempFile(request.getSno() + "_files", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFilePath))) {
for (NoteAttach file : atchList) {
Path filePath = Paths.get(ATCHFILE_UPLOAD_PATH + "/" + file.getAtchSaveNm()).toAbsolutePath().normalize();
if (!Files.exists(filePath)) {
continue; // ํ์ผ์ด ์์ผ๋ฉด ๊ฑด๋๋๋๋ค.
}
// ZIP ํ์ผ์ ์ถ๊ฐ
// String zipEntryName = file.getAtchNm() + "." + file.getAtchExtn();
String zipEntryName = file.getAtchNm();
zipOut.putNextEntry(new ZipEntry(zipEntryName));
Files.copy(filePath, zipOut);
zipOut.closeEntry();
}
}
Resource resource = new UrlResource(zipFilePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + request.getSno() + "_files.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
'Backend > JAVA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [JAVA] Client IP, Device Type ์ถ์ถ (IP ๊ธฐ๋ฐ ๋ก๊ทธ์ธ) (0) | 2025.12.18 |
|---|---|
| ๋๋ DB INSERT ์ต์ ํ (0) | 2025.11.29 |
| JUnit ๋จ์ ํ ์คํธ ์ ์ฉ, JaCoCo ์ปค๋ฒ๋ฆฌ์ง ์ธก์ (0) | 2025.08.27 |
| Stream API๋? (for-loop์ ๋น๊ต) (0) | 2025.08.09 |
| static ํค์๋ (0) | 2025.02.26 |