상세 컨텐츠

본문 제목

[SPRING] MultipartFile 게시판 이미지 업로드(1) 간단한 파일 업로드 예제

JAVA/SPRING

by ranlan 2022. 1. 10. 15:16

본문

728x90

스프링부트 JPA 게시판 만들기로 계획했을 때부터 꼭 해보고 싶었던 이미지(파일) 업로드 드디어 시작

 

Multipart란

스프링에서 제공하는 인터페이스로 웹 클라이언트가 요청을 보낼 때 HTTP 프로토콜 바디 부분에 데이터를 여러 부분으로 나눠 보내며 보통 파일 전송할 때 사용

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html

 

MultipartFile (Spring Framework 5.3.14 API)

Transfer the received file to the given destination file. This may either move the file in the filesystem, copy the file in the filesystem, or save memory-held contents to the destination file. If the destination file already exists, it will be deleted fir

docs.spring.io

 

 

환경 설정

스프링의 경우 porm.xml에 의존성을 추가해준다거나 하는데 스프링 부트에서는 기본으로 제공되는건지 딱히 추가한 건 없다.

* content-type이 multipart/form-data인 HTTP 요청 처리는 내가 프로젝트 생성할 때 이미 추가한 spring-boot-starter-web 에 포함되어 있음

 

📄 build.gradel

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    ....
}

📄 application.properties

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
  • 이미지 업로드 시 한 파일에 대한 최대 크기
  • 이미지 업로드 시 하나의 요청에 대한 최대 크기
file.upload.location=/Users/juran/Desktop/images/
  • 업로드된 파일의 임시 경로

++ 파일이 저장되는 경로 환경변수 설정은 추후에 추가할 예정

 

 

간단한 파일 업로드 예제

1. 먼저 엔티티와 DTO 생성

📦 ImageTest

@Entity
@Getter
@Setter
@NoArgsConstructor
public class ImageTest {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long imageNo;

    @Column(length = 500, nullable = false)
    private String originImageName;

    @Column(length = 500, nullable = false)
    private String imageName;

    @Column(length = 1000, nullable = false)
    private String imagePath;

    @Builder
    public ImageTest(String originImageName, String imageName, String imagePath) {
        this.originImageName = originImageName;
        this.imageName = imageName;
        this.imagePath = imagePath;
    }
}
  • 이미지 테이블의 primary key
  • 업로드한 이미지의 원래 파일 명
  • 실제 저장되는 파일 명(이름이 겹치는 것을 방지하기 위해 새로 이름을 부여할 예정)
  • 이미지가 저장되는 경로

 

📦 ImageDto

이미지 객체를 이용하기 위한 DTO

@Getter
@Setter
@NoArgsConstructor
public class ImageDto {

    private String originImageName;
    private String imageName;
    private String imagePath;

    public ImageTest toEntity() {
        ImageTest build = ImageTest.builder()
                .originImageName(originImageName)
                .imageName(imageName)
                .imagePath(imagePath)
                .build();
        return build;
    }

    @Builder
    public ImageDto (String originImageName, String imageName,String imagePath) {
        this.originImageName = originImageName;
        this.imageName = imageName;
        this.imagePath = imagePath;
    }

}
  • 원래 파일 명
  • 새로 부여한 파일 명
  • 이미지 경로
  • DTO 객체를 Entity로 변환하는 메서드 toEntity()

 

2. 레퍼지토리 작성

📄 ImageRepository

public interface ImageRepository extends JpaRepository<ImageTest, Long> {
}

 

3. 서비스 작성

📄 TestService

@Service
@RequiredArgsConstructor
public class TestService {

    private final ImageRepository imageRepository;

    @Transactional
    public Long saveImage(ImageDto imageDto) {
        return imageRepository.save(imageDto.toEntity()).getImageNo();
    }
}

이미지 DTO를 엔티티로 변환하여 DB에 저장 후 이미지No 반환

 

4. 컨트롤러 작성

📄 TestApiController

@PostMapping("/image/V1/api")
public String imageUploadV1(@RequestParam(name = "image") MultipartFile image) throws IOException {

    // 폴더 생성과 파일명 새로 부여를 위한 현재 시간 알아내기
    LocalDateTime now = LocalDateTime.now();
    int year = now.getYear();
    int month = now.getMonthValue();
    int day = now.getDayOfMonth();
    int hour = now.getHour();
    int minute = now.getMinute();
    int second = now.getSecond();
    int millis = now.get(ChronoField.MILLI_OF_SECOND);

    String absolutePath = new File("/Users/juran/Desktop").getAbsolutePath() + "/"; // 파일이 저장될 절대 경로
    String newFileName = "image" + hour + minute + second + millis; // 새로 부여한 이미지명
    String fileExtension = '.' + image.getOriginalFilename().replaceAll("^.*\\.(.*)$", "$1"); // 정규식 이용하여 확장자만 추출
    String path = "images/test/" + year + "/" + month + "/" + day; // 저장될 폴더 경로

    try {
        if(!image.isEmpty()) {
            File file = new File(absolutePath + path);
            if(!file.exists()){
                file.mkdirs(); // mkdir()과 다르게 상위 폴더가 없을 때 상위폴더까지 생성
            }

            file = new File(absolutePath + path + "/" + newFileName + fileExtension);
            imageV1.transferTo(file);

            ImageDto imgDto = ImageDto.builder()
                    .originImageName(image.getOriginalFilename())
                    .imagePath(path)
                    .imageName(newFileName + fileExtension)
                    .build();

            testService.saveImage(imgDto);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "test/imageV1";
}
  • 저장될 폴더 경로는 Desktop / {년도} / {월} / {일}
  • 이미지에 새로 부여될 파일명은 image{시}{분}{초}{밀리단위초}
    * 이미지 파일 명이 겹치는 것을 막기 위해 millisecond 단위까지 지정함
  • 저장되어야할 폴더의 유무 확인하고 없다면 생성 > mkdirs()는 없을 경우 상위 폴더까지 모두 생성해줌
  • 파일 확장자는 정규식을 이용하여 .이후의 문자열을 잘라내어 새로 이미지명 부여 후 뒤에 추가해줌
  • 이미지DTO로 변환시킨 뒤 서비스로 전달하여 다시 객체로 변환, DB에 저장
    블로그를 작성하며 보니 굳이 DTO로 변환시키지 않고 바로 객체로 만들어 저장해도 되지 않았을까.. 싶은..

* try/catch 구문으로 예외 처리하려 하였으나 이미지가 없는 경우나 여러 이미지가 들어왔을 때 등등 다양한 예외처리가 필요해 보임

 

5. 클라이언트 작성

📄 imageUpload.jsp

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Form 태그를 이용한 파일 전송</title>
  </head>
  <body>
    <form name="form" method="post" action="/test/image/V1/api" enctype="multipart/form-data">
      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
      <input type="file" name="image" multiple="multiple"/>
      <input type="submit" id="submit" value="전송"/>
    </form>
  </body>
</html>

* 현재 스프링 시큐리티가 적용된 상태라 csrf 토큰을 hidden값으로 넘겨줌

  • form으로 작성
  • 컨트롤러에서 @RequestParam(name="image") 받는 이름과 동일하게 name 설정해줄 것
  • form 데이터의 인코딩 방식 enctype="multipart/form-data"
  • <input> 태그의 multiple 속성은 <input> 요소에 사용자가 둘 이상의 값을 입력할 수 있음을 명시

 

 

실행 결과

파일 선택한 모습

 

imageUpload.jsp

전송 결과 실행된 쿼리

insert 
    into
        image_test
        (image_name, image_path, origin_image_name) 
    values
        (?, ?, ?)

지정한 경로에 이미지가 저장된 모습

 

데이터베이스에 저장된 모습

 

imageTest

 

728x90

관련글 더보기

댓글 영역