๋ฐ์ํ
์คํ๋ง MVC์์๋ MultipartResolver๋ฅผ ์ค์ ํ๋ฉด ์ดํ๋ฆฌ์ผ์ด์ ๋ค์ด์ค๋ Multipart ์์ฒญ์ ์ฒ๋ฆฌํ์ฌ ํ์ผ ์ ๋ก๋๋ฅผ ์ฝ๊ฒ ํ ์ ์๋ค.
์คํ๋ง ๋ถํธ์์๋ ๊ธฐ๋ณธ ๋น์ผ๋ก ๋ฑ๋ก๋์ด์๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ผ๋ก ๋ฑ๋กํด ์ค ํ์๊ฐ ์๋ค!
์ ๋ก๋
1. application.properties ์์ฑ
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
file.dir=D:/test/
- max-file-size : ํ์ผ ํ๋์ ์ต๋์ฌ์ด์ฆ ์ค์ , ์๋ฌด๋ฐ ์ค์ ์ ์ํ๋ฉด ๊ธฐ๋ณธ๊ฐ์ด 1MB์ด๋ค.
- max-request-size : Multipart ์์ฒญ ํ๋์ ์ฌ๋ฌ ํ์ผ์ ์ ๋ก๋ ํ ์ ์๋๋ฐ, ๊ทธ ์ ์ฒด ํฉ์ ํฌ๊ธฐ๋ฅผ ์ค์ ํด์ค ์ ์๋ค. ๊ธฐ๋ณธ๊ฐ 10MB
- file.dir : file.dir ์ด ๋ถ๋ถ์ ์ํ๋ ์ด๋ฆ์ผ๋ก ์ ๊ณ ์ํ๋ ํ์ผ์ ์ฅ๊ฒฝ๋ก๋ฅผ ์ค์ ํด์ฃผ๋ฉด ๋๋ค. ํ์ @Value("${file.dir}")๋ก ๋ถ๋ฌ๋ค๊ฐ ์ฌ์ฉํ ์์ ์ด๋ค. ๋ง์ฝ ํ์ผ ์ ์ฅ ์์น๋ฅผ ๊ฐ๊ธฐ ๋ค๋ฅด๊ฒ ํด์ผํ ๊ฒฝ์ฐ, ์ํ๋ ๊ฒฝ๋ก๋ก ์ฌ๋ฌ๊ฐ ๋ง๋ค์ด์ ๋ถ๋ฌ๋ค๊ฐ ์ฌ์ฉํ๋ฉด ํธ๋ฆฌํ๋ค. ๊ด๋ฆฌํ๊ธฐ ํธํ๋ผ๊ณ .properties์๋ค๊ฐ ์ค์ ํด๋ ๊ฒ.
2. ํผํ๋ฉด ์์ฑ
<!DOCTYPE HTML>
<head>
<meta charset="utf-8">
</head>
<body>
<h2>ํ์ผ ์
๋ก๋</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<h4>๋จ์ผ ํ์ผ ์
๋ก๋</h4>
<input type="file" name="file">
<h4>๋ค์ค ํ์ผ ์
๋ก๋</h4>
<input type="file" multiple="multiple" name="files">
<input type="submit"/>
</form>
</body>
</html>
- form ํ๊ทธ์ enctype="multipart/form-data"๋ก ์ค์ ์ ํด์ฃผ์ด์ผ ํ๋ค.
- input ํ๊ทธ์ multiple="multiple" ์ ์๋ type="file"์ input์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋์ ํ์ผ๋ง ์ ํ ๊ฐ๋ฅํ๋ฐ ์์ ์์ฑ์ ์ ์ฉํด์ฃผ๋ฉด ์ฌ๋ฌ ๊ฐ์ ํ์ผ์ ์ ํํ ์ ์๋๋ก ํด์ค๋ค.
3. ํ์ผ ์ ๋ณด๋ฅผ ๋ด์ Entity ์์ฑ
@NoArgsConstructor
@Table(name = "file")
@Entity
public class FileEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="file_id")
private Long id;
private String orgNm;
private String savedNm;
private String savedPath;
@Builder
public FileEntity(Long id, String orgNm, String savedNm, String savedPath) {
this.id = id;
this.orgNm = orgNm;
this.savedNm = savedNm;
this.savedPath = savedPath;
}
}
4. ํ์ผ ์ ๋ณด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ Repository ์์ฑ
Spring Data JPA ์ด์ฉ
public interface FileRepository extends JpaRepository<FileEntity,Long> {}
5. Multipart๋ก ๋์ด์จ ํ์ผ์ ์ฒ๋ฆฌํด ์ค Service ์์ฑ
@RequiredArgsConstructor
@Service
public class FileService {
@Value("${file.dir}")
private String fileDir;
private final FileRepository fileRepository;
public Long saveFile(MultipartFile files) throws IOException {
if (files.isEmpty()) {
return null;
}
// ์๋ ํ์ผ ์ด๋ฆ ์ถ์ถ
String origName = files.getOriginalFilename();
// ํ์ผ ์ด๋ฆ์ผ๋ก ์ธ uuid ์์ฑ
String uuid = UUID.randomUUID().toString();
// ํ์ฅ์ ์ถ์ถ(ex : .png)
String extension = origName.substring(origName.lastIndexOf("."));
// uuid์ ํ์ฅ์ ๊ฒฐํฉ
String savedName = uuid + extension;
// ํ์ผ์ ๋ถ๋ฌ์ฌ ๋ ์ฌ์ฉํ ํ์ผ ๊ฒฝ๋ก
String savedPath = fileDir + savedName;
// ํ์ผ ์ํฐํฐ ์์ฑ
FileEntity file = FileEntity.builder()
.orgNm(origName)
.savedNm(savedName)
.savedPath(savedPath)
.build();
// ์ค์ ๋ก ๋ก์ปฌ์ uuid๋ฅผ ํ์ผ๋ช
์ผ๋ก ์ ์ฅ
files.transferTo(new File(savedPath));
// ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ์ผ ์ ๋ณด ์ ์ฅ
FileEntity savedFile = fileRepository.save(file);
return savedFile.getId();
}
}
6. Controller ์์ฑ
@RequiredArgsConstructor
@Controller
public class TestController {
private final FileService fileService;
@GetMapping("/upload")
public String testUploadForm() {
return "test/uploadTest";
}
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file,@RequestParam("files") List<MultipartFile> files) throws IOException {
fileService.saveFile(file);
for (MultipartFile multipartFile : files) {
fileService.saveFile(multipartFile);
}
return "redirect:/";
}
}
์๋ ์ฑ๊ณต!
๋ค์ด๋ก๋
1. Controller ์์ฑ
์ ๋ก๋ํ ๋ ์์ฑํด๋ ์ปจํธ๋กค๋ฌ์ ์ถ๊ฐ๋ก ์์ฑํด์ค ๊ฒ ์ด๋ค.
@RequiredArgsConstructor
@Controller
public class TestController {
private final FileService fileService;
private final FileRepository fileRepository;
~ ๊ธฐ์กด ์ฝ๋ ~
@GetMapping("/view")
public String view(Model model) {
List<FileEntity> files = fileRepository.findAll();
model.addAttribute("all",files);
return "view";
}
// ์ด๋ฏธ์ง ์ถ๋ ฅ
@GetMapping("/images/{fileId}")
@ResponseBody
public Resource downloadImage(@PathVariable("fileId") Long id, Model model) throws IOException{
FileEntity file = fileRepository.findById(id).orElse(null);
return new UrlResource("file:" + file.getSavedPath());
}
// ์ฒจ๋ถ ํ์ผ ๋ค์ด๋ก๋
@GetMapping("/attach/{id}")
public ResponseEntity<Resource> downloadAttach(@PathVariable Long id) throws MalformedURLException {
FileEntity file = fileRepository.findById(id).orElse(null);
UrlResource resource = new UrlResource("file:" + file.getSavedPath());
String encodedFileName = UriUtils.encode(file.getOrgNm(), StandardCharsets.UTF_8);
// ํ์ผ ๋ค์ด๋ก๋ ๋ํ์์๊ฐ ๋จ๋๋ก ํ๋ ํค๋๋ฅผ ์ค์ ํด์ฃผ๋ ๊ฒ
// Content-Disposition ํค๋์ attachment; filename="์
๋ก๋ ํ์ผ๋ช
" ๊ฐ์ ์ค๋ค.
String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,contentDisposition).body(resource);
}
}
- UrlResource ๋ก ํ์ผ์ ์ฝ์ด์ @ResponseBody ๋ก ์ด๋ฏธ์ง ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋ฐํ
- UrlResource๋ Resource ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด๋ก new UrlResource("file:" + "ํ์ผ์ด ์ ์ฅ๋ ๊ฒฝ๋ก") ๋ก ์ฌ์ฉํ๋ฉด๋๋ค
- contentDisposition ๊ฐ์ ๋ ์ด๋ฆ์ผ๋ก ํ์ผ์ด ์ค์ง์ ์ผ๋ก ์ ์ฅ๋๋ค.
- ๋ง์ฝ ์์์ UriUtils.encode(file.getSavedNm(),~) ๋ก ์ฃผ์๋ค๋ฉด ์ค์ ํ์ผ์ ๋ค์ด๋ก๋ ๋ฐ์ ๋ ํ์ผ๋ช ์ด uuid๋ก ํด๋์ ํ์ผ๋ช ์ผ๋ก ๋์จ๋ค.
2. View ์์ฑ
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>์ด๋ฏธ์ง ์ถ๋ ฅ</h3>
<div th:each="imageFile : ${all} ">
<img th:src="|/images/${imageFile.id}|" width="150" height="150">
<p th:text="${imageFile.orgNm}"></p>
</div>
<h3>ํ์ผ ๋ค์ด๋ก๋</h3>
<div th:each="file : ${all}">
<a th:href="|/attach/${file.id}|" th:text="${file.orgNm}"></a>
</div>
</body>
</html>
์คํ ๊ฒฐ๊ณผ
ํ์ผ ์ ๋ก๋๋ ๋์ถฉ ์ด๋ฐ ์์ผ๋ก ํ๋ฌ๊ฐ๋ค๋ ๊ฒ์ ๊ธฐ์ตํ์.
๋ฐ์ ๋ค์ด๋ก๋๋ content disposition์ attachment; filename="ํ์ผ๋ช " ์ ๋ณํ๋ฅผ ์ฃผ์์ ๋ ์ด๋ป๊ฒ ์ ์ฅ๋๋์ง ํท๊ฐ๋ ค์ ํ ์คํธ ํด๋ณธ ํ์ ์ด๋ค.
References
๋ฐ์ํ
'Backend๐ฑ > Spring JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[์ํคํ ์ฒ] Entity vs DTO vs VO ๊ฐ๋ ์ ๋ฆฌ (0) | 2023.07.29 |
---|---|
DTO vs ์ํฐํฐ (0) | 2023.07.29 |
[Spring Boot] Lombok @Getter Boolean ํ์ (0) | 2023.05.26 |
[JPA] ๊ธฐ๋ณธ์์ฑ findBy.. ํจ์๋ค๊ณผ return๊ฐ (0) | 2023.05.20 |
[JPA๐ฑ] ์ฌ๋ฏธ์๋ JPA ๊ฐ ํ์ ์ปฌ๋ ์ @ElementCollection !! (0) | 2023.05.11 |