[코드로 배우는 스프링 부트 웹 프로젝트] 스프링 부트(Spring Boot) 03

hidihyeonee·2025년 2월 18일
0

2025.02.18 작성

OS : Window
개발환경: IntelliJ IDEA
개발언어: Java
프레임워크: Spring Boot


package org.zerock.board.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "writer")

public class Board extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String bno;

    private String title;

    private String content;

    @ManyToOne
    private Member writer; // 연관관계 지정
}

@ManyToOne (다대일 관계, Many-to-One)란?

JPA에서 테이블 간 관계를 설정할 때 사용하는 어노테이션 중 하나.

즉, "게시글(Board)이 회원(Member)와 어떤 관계인지"를 정의하는 역할을 함.

회원 (Member) = 한 명
게시글 (Board) = 여러 개
한 명의 회원이 여러 개의 게시글을 작성할 수 있음 → @ManyToOne 사용!

@ToString이 하는 역할

@ToString은 Lombok에서 제공하는 기능으로, 객체의 필드 값을 문자열로 변환하는 역할을 함.

즉, System.out.println(board); 같은 코드를 실행하면 toString()이 자동으로 호출되어 객체 내용을 출력할 수 있음.

ToString(exclude = "writer")를 왜 사용하는가?

✔️ 1. 무한 재귀 호출을 방지하기 위해!
✔️ 2. @ManyToOne 관계에서 성능 최적화를 위해!
✔️ 3. writer 정보가 필요하면 board.getWriter()를 통해 직접 조회하면 됨!

오류 주의

- @GeneratedValue(strategy = GenerationType.IDENTITY) 어노테이션은 기본 키(auto-increment) 필드에 사용되는데, 현재 email 필드는 String 타입.

- 그러나 IDENTITY 전략은 숫자형(primary key) 필드에서만 동작하기 때문에 String email에는 사용할 수 없습니다.


Movie

uploadEx.html

<input name="uploadFiles" type="file" multiple>
<button class="uploadBtn">Upload</button>

<script>
    $(".uploadBtn").click(function() {
        var formData = new FormData();
        var inputFile = $("input[type='file']");
        var files = inputFile[0].files;

        for (var i = 0; i < files.length; i++) {
            console.log(files[i]);
            formData.append("uploadFiles", files[i]);
        }

        $.ajax({
            url: '/uploadAjax',
            processData: false,
            contentType: false,
            data: formData,
            type: 'post',
            dataType: 'json',
            success: function(result) {
                console.log(result);
            },
            error: function(xhr, status, error) {
                console.log(status);
            }
        });
    });
</script>
  1. 사용자가 파일을 선택 ()
  2. "Upload" 버튼 클릭 시 AJAX 요청 전송.
    • FormData를 생성하여 파일을 추가. (formData.append("uploadFiles", files[i]))
    • POST /uploadAjax로 서버에 전송.
  3. 성공 시 (success 콜백 함수)
    • 서버 응답을 console.log(result)로 출력.
  4. 실패 시 (error 콜백 함수)
    • 오류 로그 출력.

📌 여러 개의 파일을 한 번에 업로드할 수 있도록 multiple 속성이 적용됨.

UploadController.java

@RestController
@Log4j2
public class UploadController {

    @Value("${org.zerock.upload.path}")
    private String uploadPath;

    @PostMapping("/uploadAjax")
    public void uploadFile(MultipartFile[] uploadFiles) {
        for (MultipartFile f : uploadFiles) {
            // 1️⃣ 이미지만 허용
            if (f.getContentType().startsWith("image") == false) {
                log.warn("this file is not image type");
                return;
            }

            String originalFilename = f.getOriginalFilename();
            String fileName = originalFilename.substring(originalFilename.lastIndexOf("\\") + 1);

            log.info("fileName:" + fileName);

            // 2️⃣ 날짜별 폴더 생성
            String folderPath = makeFolder();

            // 3️⃣ UUID 적용하여 저장 (파일명 중복 방지)
            String uuid = UUID.randomUUID().toString();
            String saveName = uploadPath + File.separator + folderPath + File.separator + uuid + "_" + fileName;
            Path savePath = Paths.get(saveName);

            try {
                // 4️⃣ 파일 저장
                f.transferTo(savePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String makeFolder() {
        String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        String folderPath = str.replace("/", File.separator);

        File uploadPathFolder = new File(uploadPath, folderPath);
        if (!uploadPathFolder.exists()) {
            uploadPathFolder.mkdirs();
        }
        return folderPath;
    }
}
  1. 이미지 파일인지 확인 (startsWith("image"))
    • image MIME 타입이 아닌 파일이면 log.warn() 출력 후 업로드 중단.
  2. 파일명 정리
    • 원본 파일명에서 디렉토리 경로를 제거 (fileName)
  3. 저장 폴더 생성 (makeFolder())
    • yyyy/MM/dd 형식으로 날짜별 폴더 생성.
    • 예: 2025/02/15 → uploadPath/2025/02/15/
  4. 파일명 충돌 방지를 위한 UUID 적용
    • UUID.randomUUID().toString() + "_" + fileName 형식으로 저장.
    • 예: f2b4a1e1-2345-6789-abcd-123456789abc_image.jpg
      - UUID : 범용 고유 식별자.
  5. 파일 저장 (f.transferTo(savePath))
    • 지정된 경로에 파일을 저장.

makeFolder() → 날짜별 폴더 생성

private String makeFolder() {
    String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    String folderPath = str.replace("/", File.separator);

    File uploadPathFolder = new File(uploadPath, folderPath);
    if (!uploadPathFolder.exists()) {
        uploadPathFolder.mkdirs();
    }
    return folderPath;
}

폴더 생성 과정

  • 현재 날짜(yyyy/MM/dd)로 폴더명 생성
  • /을 OS에 맞는 경로 구분자(File.separator)로 변환
  • 해당 폴더가 존재하지 않으면 mkdirs()로 생성
    📌 결과 예시
    오늘 날짜가 2025-02-15라면 → /uploadPath/2025/02/15/ 폴더가 생성됨.

AJAX를 이용하여 비동기 업로드 처리

--> 현재 파일 업로드 기능을 구현할 때 AJAX를 사용하여 비동기 처리하고 있음.
즉, 페이지를 새로고침하지 않고 파일을 서버에 업로드하는 방식.

Thumbnailator란?

Thumbnailator는 Java에서 이미지 리사이징(크기 조정), 포맷 변환, 품질 조절 등을 간편하게 수행할 수 있는 라이브러리.

🔥 왜 필요한데?

Java에서 기본적으로 제공하는 ImageIO나 Graphics2D 같은 클래스를 직접 사용해서 이미지를 다루려면 코드가 복잡하고 성능도 좋지 않음.

Thumbnailator를 쓰면 몇 줄의 코드만으로 고품질의 썸네일을 만들 수 있음.

크기 조절, 포맷 변환, 품질 조절, 회전, 워터마크 추가 등이 가능.

profile
벨로그 쫌 재밌네?

0개의 댓글