Spring/Blog-V2

스프링 54강. multipart/form-data

JJJAEOoni 2022. 3. 31. 01:26
반응형

 

 

파일 업로드를 위한 샘플 코드를 만들어볼 것이다.

 

프로젝트 생성하는 과정이 이전과 조금 다르다.

 

 

H2 데이터베이스는 임시 데이터베이스라고 보면 된다.

연습용으로 많이 사용된다.

in-memoory 데이터베이스라고 해서 데이터가 메모리 형태로 들어가서 하드에 기록이 되지 않는다.

 

server:
  port: 8080
  servlet:
    encoding:
      charset: utf-8
      
spring:
  datasource:
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  h2:
    console:
      enabled: true
  jpa:
    open-in-view: true
    hibernate:
      ddl-auto: create # create, update, none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true

 

샘플 코드를 짤 때 필요한 것만 적어두는 게 좋다.

다른 사람들이 이 코드를 봤을 때 혼란이 올 수도 있으니까

최대한 심플하게 만드는 게 핵심이다!

 

mem : 사용자 이름

test : 데이터베이스 명

이게 h2의 디폴트 값이다.

 

간단한 데이터베이스 구축을 해야 한다.

엔티티부터 만들자.

 

package site.metacoding.fileupload;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {

    @Id // PK
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String username;

    private String imgUrl; // 사진의 경로
}

 

파일은 파일 시스템에 저장해야 하고

데이터베이스는 데이터베이스 시스템이다.

 

파일을 디비에 저장하지는 않는다.

경로를 저장해주자.

 

파일을 받아서 DB에 INSERT 할 거니까 POST 요청이다.

따로 Dto를 만들 필요 없이 User 타입으로 받자.

 

근데 파일은 유저 타입으로 받을 수 없다.

 

사진의 MIME 타입이 image/png라면

서버 쪽으로 전송했을 때 서버는 사진을 받을까, 사진의 url을 받을까?

 

image/png 타입의 사진을 받는다.

 

a.png 사진 자체가 바이트로 바뀌어서 서버로 전송되는 것이다. 

실제로 url이 아닌 파일을 받는다.

 

이때 서버는 1번으로 image/png를 받아서

이미지 역직렬화를 해야 한다.

역직렬화가 끝나면 파일을 다루는 자바 오브젝트로 바뀔 것이다.

 

그리고 2번으로 이미지 스트림을 파일에 연결한다.

그럼 파일이 만들어지는데,

 

마지막으로 3번이 가장 중요하다.

경로와 파일명을 DB에 저장한다.

 

 

우리가 회원가입을 하기 위해 데이터를 전송할 때는

1. 이미지랑 2. 스트링(username)을 같이 보내야 한다.

 

클라이언트는 이미지를 주는 거고, 이미지를 파일로 써서 그 경로를 디비에 저장하는 것이다.

클라이언트는 이미지를 주지 imgUrl(String)을 주는 게 아니다.

 

그래서 User 타입으로 받을 수 없다.

 

두 개의 상이한 MIME 타입의 데이터를 하나의 바디 데이터로 보낼 때는

multipart/form-data 라는 MIME 타입을 사용한다.

타입이 여러 가지가 섞여있을 때 사용한다.

 

파트별로 분리하여 여러 가지 데이터 타입을 보낼 수 있다.

 

User타입으로 받을 수 없으니까 Dto를 만들어야 한다.

User타입으로 받으면 사진 파일을 받을 수 있는 타입이 없기 때문이다.

 

실제 파일은 서버에서 쓰고, 그 파일의 url을 User 오브젝트에 넣어서 디비에 save 해야 한다.

 

 

JoinDto

package site.metacoding.fileupload;

import org.springframework.web.multipart.MultipartFile;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class JoinDto {
    private String username; // form 태그 name="username"
    private MultipartFile file; // form 태그 name="file"
}

 

json으로 받을게 아니라 form 태그로 전송받을 것이기 때문에 @RequestBody가 필요 없다.

 

버퍼로 읽을 수 있는 게

 

1. json을 자바 오브젝트로 바꾸고 싶을 때

2. 있는 그대로의 데이터를 받고 싶을 때

 

버퍼로 읽으면 다시 파싱 해야 하는데

requestBody를 걸면 json은 자동 파싱 해주는데 이미지는 안 해준다.

 

스프링에는 그런 전략 없다!!

 

package site.metacoding.fileupload;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RestController
public class UserController {

    private final UserRepository userRepository;

    // 파일을 받아서 DB에 INSERT : POST 요청
    @PostMapping("/user")
    public String join(JoinDto joinDto) {

        String requestFileName = joinDto.getFile().getOriginalFilename();
        System.out.println("전송받은 파일명 : " + requestFileName);

        return requestFileName;
    }
}

 

잘 전송받았는지 포스트맨으로 테스트해보자.

 

 

 

잘 전송되었다.

 

지금까지는 파일을 받기만 했지 하드에 저장한 것은 아니다.

지금은 파일 데이터가 메모리에 저장되어있고 하드에 써줘야 한다.

메모리에 있는 파일 데이터를 파일 시스템으로 옮겨주는 것이다.

 

1. 빈 파일을 생성해준다.

자바에서 제공해주는 File 객체를 만들어준다.

 

2. 빈 파일에 스트림을 연결한다.

 

3. 이미 파일을 다운을 받아서 크기는 알고 있기 때문에

버퍼로 읽을 필요 없이 FileWriter 객체를 이용해

for문 돌리면서 바이트로 쓰면 된다.

 

크기를 모르기 때문에 BufferedWriter를 써야 했는데 이게 필요 없는 것이다.

 

이를 스프링에서는 간단한 라이브러리로 제공해준다.

 

통신이니까 try catch를 걸어줘야 한다.

 

write 메서드의 첫 번째 인수는 path인데

path는 객체를 하나 만들어줘야 한다.

 

패스 경로를 지정해줄 때 주의해야 할 점이 있다.

 

1. 경로 폴더가 이미 만들어져 있어야 한다.

2. 경로를 지정할 때 리눅스는 /를 이용하고, 윈도우는 \를 사용한다.

 

파일의 path에는 파일 이름까지 들어와야 한다.

 

두 번째 인수에는 실제 파일이 들어와야 한다.

 

joinDto.get( )이 실제 파일인데 얘는 MultipartFile을 리턴해준다.

MultipartFile는 바이트가 아니기 때문에

joinDto.get( ).getByte( )라고 만들어져 있는 메서드를 사용해준다.

 

이제 포스트맨으로 실행해보자.

 

오류가 안 난다면 실제 upload 폴더에 저장되었는지 확인해봐야 한다.

잘 들어왔다.

 

이제 이 파일이 저장된 경로를 실제 DB에 저장해줘야 한다.

DB에는 풀 경로를 저장해야 할까, 파일명만 저장하면 될까?

 

풀 경로라면 imgUrl = C:/upload/a.png인데

내가 만약 리눅스에 던질 때

리눅스의 최상위 경로는 C가 아니라 /이기 때문에 꼬이게 된다.

 

또한 내가 upload 폴더에 사진이 5000장 정도 있는 상태에서

폴더를 옮기게 되어 C:/workspace/upload 가 경로가 되면

이미 DB에 저장되어있는 데이터를 모두 다 바꿔줘야 한다. 힘들다.

 

그렇기 때문에 파일명만 DB에 저장하고

경로는 그때그때 바뀔 수 있어야 하니까 따로 저장해두어야 한다.



 

[출처]

 

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com

메타 코딩 유튜브

https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 
반응형