Spring Boot (+ RESTful)

RESTful 웹 서비스 구축 - # Project 04 - 이미지 업로드 (2)

wy-family 2025. 1. 4. 15:25

지난 게시글에 작성했던 질문에 대한 답을 기록한다. 

질문 1: @PostMapping의 {"multipart/form-data"}에서 {}는 무엇을 의미하나요?

{}는 Java에서 배열(array)을 나타내는 문법이에요. consumes는 이 API가 처리할 데이터 형식(MIME 타입)을 배열로 받을 수 있습니다.

  • 예시:
    • "application/json": 단일 값 (JSON 형식만 처리)
    • {"multipart/form-data"}: 배열 형식으로 하나 이상의 데이터 형식을 처리할 수 있음.

여기서는 배열로 지정했기 때문에 확장성을 고려한 것으로 보입니다. 실제로 다른 형식도 추가하려면 배열에 더 넣을 수 있습니다:

consumes = {"multipart/form-data", "application/json"}

질문 2: multipart/form-data 형식이란 무엇인가요?

multipart/form-data는 주로 파일 업로드에 사용되는 HTTP 요청 형식입니다. 이 형식은 데이터를 여러 부분으로 나누어 전송하며, 각 부분은 파일이나 텍스트 데이터일 수 있습니다.

  • 예: 사용자가 이미지를 업로드할 때 multipart/form-data는 다음처럼 동작합니다:
Content-Type: multipart/form-data; boundary=abc123

--abc123
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

[파일의 바이너리 데이터]
--abc123--

질문 3: @RequestPart MultipartFile[] files의 각 부분에 대해 설명해주세요.

@RequestPart

  • REST 요청에서 multipart 데이터를 받아오는 데 사용됩니다.
  • 클라이언트가 업로드한 파일이나 데이터를 메서드의 파라미터로 매핑합니다.

MultipartFile

  • 스프링 프레임워크에서 제공하는 인터페이스로, 업로드된 파일을 다룰 수 있도록 돕습니다.
  • 주요 메서드:
    • getOriginalFilename(): 파일의 원래 이름.
    • getInputStream(): 파일 내용을 읽는 스트림.
    • getContentType(): 파일의 MIME 타입.
    • getSize(): 파일 크기.

[] (배열)

  • 업로드된 여러 개의 파일을 처리하기 위해 MultipartFile 배열로 선언합니다.

[]와 <>의 차이

  • []: 배열입니다. 고정된 크기의 연속된 데이터 구조를 나타냅니다.
  • <>: 제네릭입니다. 예를 들어, List<>는 크기가 가변적이고 배열보다 더 유연합니다.

질문 4: Arrays.asList(files).stream().forEach(file -> { ... })의 의미

1. Arrays.asList(files)

  • files 배열을 리스트(List)로 변환합니다.

2. .stream()

  • 리스트를 스트림(Stream)으로 변환합니다. 스트림은 데이터 요소를 하나씩 처리하는 Java의 API입니다.

3. .forEach(file -> { ... })

  • 스트림에 있는 각 요소(file)를 하나씩 꺼내와 처리합니다.
  • file은 MultipartFile 객체입니다.

예를 들어, files 배열에 3개의 파일이 있다면, forEach는 각 파일에 대해 한 번씩 실행됩니다.


질문 5: file.getContentType()의 MIME 타입이란 무엇인가요?

MIME 타입은 파일의 종류와 형식을 나타냅니다. 예:

  • image/png: PNG 이미지 파일.
  • image/jpeg: JPEG 이미지 파일.
  • application/pdf: PDF 문서 파일.

질문 5-1: image/png처럼 image/가 붙는 이유는?

MIME 타입은 주 타입하위 타입으로 나뉩니다.

  • 주 타입: 파일의 범주(예: image, application).
  • 하위 타입: 파일의 구체적인 형식(예: png, pdf).

예를 들어, PDF 파일의 경우 file.getContentType()은 application/pdf를 반환합니다.


질문 6: PNG, JPG, JPEG란 무엇이고 차이점은?

  • PNG (Portable Network Graphics):
    • 무손실 압축 포맷으로 품질이 유지됩니다.
    • 배경이 투명한 이미지를 지원합니다.
  • JPG/JPEG (Joint Photographic Experts Group):
    • 손실 압축 포맷으로 파일 크기를 줄이기 위해 일부 품질이 손실됩니다.
    • JPG와 JPEG는 같은 형식입니다. 파일 확장자가 다를 뿐입니다.

질문 7: file.getOriginalFilename()의 역할과 MultipartFile

MultipartFile에는 업로드된 파일과 관련된 정보가 저장됩니다. 주요 메서드:

  • getOriginalFilename(): 파일의 원래 이름.
  • getInputStream(): 파일 내용을 읽는 스트림.
  • getSize(): 파일 크기.
  • isEmpty(): 파일이 비었는지 확인.

질문 8: RandomStringUtils.random()

이 메서드는 Apache Commons Lang 라이브러리에서 제공되며, 랜덤 문자열을 생성합니다.

  • random(int count, boolean useLetters, boolean useNumbers)
    • count: 생성할 문자열의 길이.
    • useLetters: 문자 포함 여부.
    • useNumbers: 숫자 포함 여부.

질문 9: ImageUtil.makePath

ImageUtil.makePath는 업로드 경로를 생성하고, 디렉터리를 만듭니다.

  1. uploadPath + book_id: 특정 책 ID를 기반으로 경로를 생성.
  2. Files.createDirectories: 디렉터리가 없으면 새로 생성.
  3. 반환값은 완전한 파일 경로.

질문 10: Path 타입과 Paths.get()

  • Path: Java NIO에서 파일 경로나 디렉터리를 나타내는 클래스.
  • Paths.get(String path): 문자열 경로를 Path 객체로 변환.

질문 11: Files.copy와 type=1에서의 차이

Files.copy

  • 파일을 특정 경로에 복사합니다.
  • StandardCopyOption.REPLACE_EXISTING: 이미 파일이 있으면 덮어씁니다.

type=1

  • 썸네일 파일을 처리하기 위해 ImageIO.write를 사용합니다.
  • 이유: Files.copy는 일반 파일만 복사할 수 있지만, 썸네일 생성은 이미지를 변환하고 저장해야 하기 때문입니다.

질문 12: ImageUtil.getThumbnail

이 메서드는 원본 이미지를 읽고, 주어진 너비로 크기를 조정합니다.

  1. ImageIO.read: 원본 이미지를 읽어들입니다.
  2. Scalr.resize: 이미지를 주어진 크기로 변환.
  3. 반환값: 크기가 조정된 썸네일 이미지.

질문 13: ImageIO.write

이미지를 파일로 저장하는 메서드입니다.

  • 첫 번째 파라미터: 저장할 BufferedImage.
  • 두 번째 파라미터: 파일 형식(예: png).
  • 세 번째 파라미터: 저장할 파일 경로.

질문 14: try-catch와 printStackTrace

  • try-catch:
    • 오류가 발생할 가능성이 있는 코드를 try 블록에 작성.
    • 오류가 발생하면 catch 블록이 실행됩니다.
  • printStackTrace:
    • 오류가 발생한 위치와 원인을 출력합니다.

질문 15: HashMap이란?

HashMap은 키-값 쌍으로 데이터를 저장하는 자료구조입니다.

  • HashMap<String, List<String>>: 키는 String, 값은 List<String> 타입.
  • List<HashMap<String, List<String>>>: HashMap 객체들의 리스트.

질문 15-1: put vs add

  • put: HashMap에 키-값 쌍을 추가.
  • add: 리스트에 요소를 추가.

차이는 자료구조의 동작 방식에서 옵니다.



질문 4에 대한 답변에 대한 추가 질문.

4-1. 왜 굳이 files 배열을 리스트(List)로 변환해줘? 그냥 files 자체에서 반복문을 할 방법은 없는건가?

4-2. 리스트를 스트림(Stream)으로 변환한다고 했고 스트림은 데이터 요소를 하나씩 처리하는 Java의 API 라고 했는데 이해가 잘 안 되서 말이야. 스트림이 도대체 뭐야? 처음부터 차근차근 설명해주라. 그리고, 리스트를 왜 스트림으로 바꿔야 하는건데?
4-3. .forEach(thing -> { ... }) 라는 건, stream() 의 메서드인거지?

질문 5에 대한 답변에 대한 추가 질문.

5-1. MIME 타입은 그럼 뭘 줄임말인거지?
5-2. 그러면 대표적인 파일들을 가지고 주 타입과 하위 타입을 알려줘볼래?

5-3. 그러면 application/json, multipart/form-data 이것도 MIME 타입으로 주 타입과 하위 타입으로 보는건가?

질문 6에 대한 답변에 대한 추가 질문.

6-1. 파일 확장자가 다를 뿐입니다라고 했는데 이게 무슨 말인거야? 같은 형식인데 파일 확장자가 다르다니.

 

질문 9에 대한 답변에 대한 추가 질문.

이거는 너가 다시 내가 공유해주는 코드를 보고, 정말 하나하나씩 차근 차근 상세하고 구체적으로 설명을 해주면 좋겠어.

public static String makePath(String uploadPath, String fileName, Long book_id) throws IOException {
    String path = uploadPath+book_id;
    Files.createDirectories(Paths.get(path));
    return new File(path).getAbsolutePath()+"\\"+fileName;
    // 업로드할 파일의 절대 경로를 만들기
}
public static BufferedImage getThumbnail(MultipartFile originFile, Integer width) throws IOException {
    BufferedImage thumbImg = null;
    BufferedImage img = ImageIO.read(originFile.getInputStream());
    thumbImg = Scalr.resize(img, Scalr.Method.AUTOMATIC, Scalr.Mode.AUTOMATIC, width, Scalr.OP_ANTIALIAS);
    return thumbImg; // 썸네일 이미지
}

 

질문 10에 대한 답변에 대한 추가 질문.

10-1. 근데 그냥 문자열을 가지고 경로 값으로 사용하면 될텐데 왜 굳이 파일 경로나 디렉토리를 나타내는 클래스인 Path가 필요한거야? 그냥 경로를 나타내는 문자열과 Path 는 어떤 차이가 있는거야?

질문 13에 대한 답변에 대한 추가 질문.

13-1. BufferedImage 가 뭐지?

질문 15에 대한 답변에 대한 추가 질문.

15-1. 키-값 쌍으로 데이터를 저장하는 자료구조는 HashMap 말고도 있었던 것 같은데, 맞지? 걔네들이랑 HashMap 이랑 차이가 뭐야? 그리고 HashMap 은 Hash랑 Map 이 합쳐진 것 같은데 Hash는 뭐고, Map 은 뭐야?

 

질문 4-1: 왜 files 배열을 리스트(List)로 변환하나요? 그냥 files 자체에서 반복문을 할 방법은 없나요?

배열 자체에서 반복문 사용

Java에서는 배열(files)에 직접 for나 for-each 반복문을 사용할 수 있습니다:

for (MultipartFile file : files) {
    // 파일 하나씩 처리
}

또는:

for (int i = 0; i < files.length; i++) {
    MultipartFile file = files[i];
    // 파일 하나씩 처리
}

리스트로 변환하는 이유

Arrays.asList(files)로 배열을 리스트로 변환하는 이유는 Java의 스트림(Stream) API를 활용하기 위해서입니다.

  • 스트림 API는 리스트와 같은 컬렉션 데이터를 효율적으로 처리할 수 있는 메서드 체인을 제공합니다.
  • forEach, map, filter 같은 메서드를 사용해 데이터를 더 간결하고 직관적으로 처리할 수 있습니다.

따라서, 스트림 API를 사용하지 않을 거라면 배열 그대로 반복문을 사용하는 것도 가능합니다.


질문 4-2: 스트림(Stream)이란 무엇인가요?

스트림의 개념

스트림(Stream)은 Java에서 데이터를 처리하기 위한 연속적인 데이터 흐름입니다. 데이터를 하나씩 읽어오며, 이를 변환하거나 필터링하거나 조작할 수 있습니다.

스트림의 특징

  1. 데이터를 한 번만 처리:
    • 스트림은 데이터를 "한 번" 처리하고, 결과를 반환합니다.
  2. 컬렉션과 배열을 처리:
    • 스트림은 리스트, 배열 등의 데이터 구조와 함께 사용됩니다.
  3. 함수형 프로그래밍 지원:
    • 스트림 API는 람다식(file -> {})과 함께 간결한 코드를 작성하도록 도와줍니다.

왜 스트림을 사용하나요?

  1. 가독성: 데이터 처리 작업을 간결하게 표현합니다.
    • 예: filter, map, forEach 메서드.
  2. 효율성: 대규모 데이터 처리에 적합합니다.

질문 4-3: .forEach(thing -> { ... })는 스트림의 메서드인가요?

네, 맞습니다! .forEach()는 스트림 API의 메서드로, 스트림의 각 요소에 대해 작업을 수행합니다.

  • thing -> { ... }: 람다식으로, 각 요소(thing)에 대해 실행할 작업을 정의합니다.

질문 5-1: MIME 타입은 무엇의 줄임말인가요?

MIME은 Multipurpose Internet Mail Extensions의 약자입니다.

  • 원래 이메일에서 파일 형식을 식별하기 위해 도입되었습니다.
  • HTTP에서도 파일이나 데이터의 형식을 나타내는 용도로 사용됩니다.

질문 5-2: 대표적인 MIME 타입

  • 이미지:
    • image/png: PNG 이미지.
    • image/jpeg: JPEG 이미지.
  • 문서:
    • application/pdf: PDF 파일.
    • application/msword: Word 파일.
  • 텍스트:
    • text/plain: 일반 텍스트 파일.
    • text/html: HTML 문서.

질문 5-3: application/json과 multipart/form-data도 MIME 타입인가요?

네, 맞습니다! 이것들도 MIME 타입입니다.

  • application/json:
    • 주 타입: application
    • 하위 타입: json
  • multipart/form-data:
    • 주 타입: multipart
    • 하위 타입: form-data

질문 6-1: JPG와 JPEG는 같은 형식인데 왜 파일 확장자가 다른가요?

JPG와 JPEG는 같은 파일 형식입니다. 차이가 있는 이유는 과거 Windows 운영체제의 파일 확장자 제한 때문입니다:

  • JPEG: 원래 확장자.
  • JPG: Windows가 파일 확장자를 3자로 제한했을 때 사용.

질문 9: makePath 코드 설명

코드:

public static String makePath(String uploadPath, String fileName, Long book_id) throws IOException {
    String path = uploadPath + book_id; // 업로드 경로와 책 ID를 합침
    Files.createDirectories(Paths.get(path)); // 디렉터리가 없으면 생성
    return new File(path).getAbsolutePath() + "\\" + fileName; // 파일 경로 반환
}

상세 설명

  1. String path = uploadPath + book_id;:
    • uploadPath는 기본 업로드 경로입니다(예: /uploads/).
    • book_id는 특정 책의 ID를 나타냅니다(예: 123).
    • 합치면 /uploads/123처럼 됩니다.
  2. Files.createDirectories(Paths.get(path));:
    • 해당 경로가 없으면 디렉터리를 생성합니다.
  3. return new File(path).getAbsolutePath() + "\\" + fileName;:
    • 파일의 절대 경로를 반환합니다.
    • 반환 값은 /uploads/123/myfile.png와 같은 전체 파일 경로입니다.

질문 10-1: Path와 문자열의 차이점

문자열(String)

  • 단순히 경로를 나타내는 텍스트입니다.
  • 경로 조작이나 유효성 검사가 어렵습니다.

Path

  • Java NIO에서 파일 시스템 경로를 나타내는 클래스입니다.
  • 경로 조작, 파일 존재 여부 확인 등이 쉽게 가능합니다.

질문 13-1: BufferedImage란 무엇인가요?

BufferedImage는 Java에서 이미지를 메모리에 표현하는 클래스입니다. 이미지의 픽셀 데이터를 직접 조작하거나 처리할 수 있습니다.


질문 15-1: HashMap과 다른 자료구조의 차이

HashMap

  • 키-값 쌍으로 데이터를 저장.
  • 빠른 데이터 검색과 삽입이 가능.

Map

  • 데이터 저장 방식을 정의하는 인터페이스.

Hash

  • 데이터를 해시 함수로 변환하여 저장. 데이터를 빠르게 찾을 수 있습니다.

다른 자료구조:

  • TreeMap: 키를 정렬된 상태로 저장
  • LinkedHashMap: 삽입 순서를 유지

 

일단은, 여기서 끊고 다음 게시글에서 계속 이어서 공부해보겠다.