[스프링부트] 파일 업로드v1

송송승현's avatar
Dec 05, 2024
[스프링부트] 파일 업로드v1

multiparty/form- data를 이용한 파일 업로드

  • Gradle
plugins { id 'java' id 'org.springframework.boot' version '3.4.0' id 'io.spring.dependency-management' version '1.1.6' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-mustache' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
  • properties
server.servlet.encoding.charset=UTF-8 server.servlet.encoding.force=true spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
  • View - mustache
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> </head> <body> <h1>사진 파일 전송</h1> <hr> <form action="/v1/upload" method="post" enctype="multipart/form-data"> <input type="text" name="username"><br> <input type="file" name="img" accept="image/*"><br> <input type="submit"> </form> </body> </html>
  • WebConfig
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { WebMvcConfigurer.super.addResourceHandlers(registry); // 1. 절대경로 file:///c:/upload/ // 2. 상대경로 file:./upload/ registry .addResourceHandler("/upload/**") // html에서 경로를 적으면 .addResourceLocations("file:" + "./images/") // 웹서버의 /images/ 폴더 경로를 찾음 .setCachePeriod(60 * 60); // 초 단위 => 한시간 } }
  • Entity
import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity @NoArgsConstructor @Table(name = "upload_tb") public class Upload { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String profileUrl; @Builder public Upload(Integer id, String username, String profileUrl) { this.id = id; this.username = username; this.profileUrl = profileUrl; } }
  • DTO
import lombok.Data; import org.springframework.web.multipart.MultipartFile; public class UploadRequest { @Data public static class V1DTO{ private String username; private MultipartFile img; public Upload toEntity(String profile){ return Upload.builder().username(username).profileUrl(profile).build(); } } }
  • Controller
import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @Controller @RequiredArgsConstructor public class UploadController { private final UploadService uploadService; // form 태그 이용 @GetMapping("/file1") public String file1() { return "file1"; } // View에서 데이터를 v1DTO로 매핑해서 받음 @PostMapping("/v1/upload") public String v1Upload(UploadRequest.V1DTO v1DTO) { uploadService.v1사진저장(v1DTO); return "index"; } }
  • Service
import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; @RequiredArgsConstructor @Service public class UploadService { private final UploadRepository uploadRepository; @Transactional public void v1사진저장(UploadRequest.V1DTO v1DTO){ // 1. DTO에 사진파일명을 롤링 시킨다. // UUID를 사용하여 파일에 고유식별자를 붙인다. String imgName = UUID.randomUUID()+"_"+v1DTO.getImg().getOriginalFilename(); String profileUrl = "images/"+imgName; String dbUrl = "/upload/"+imgName; // 2. DTO에 사진을 파일로 저장 (images 폴더) try { //images라는 폴더에 파일을 저장 Path path = Paths.get(profileUrl); Files.write(path, v1DTO.getImg().getBytes()); // DB에 위치 + 고유식별자 + 파일이름을 저장 uploadRepository.save(v1DTO.toEntity(dbUrl)); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } } }
  • Repository
import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor public class UploadRepository { private final EntityManager em; public void save(Upload upload) { em.persist(upload); } }
Share article

송승현의 블로그