이전글 보기

https://aamoos.tistory.com/685

 

[Spring Jpa] 14. 게시판 만들기 - 멀티파일 업로드

목표 - 파일을 여러개 올릴수 있는 input을 추가한후 멀티파일 업로드 기능을 구현하려고 합니다. 이번장에서는 파일 업로드와, File 테이블에 업로드한 파일의 정보만 쌓는 부분만 개발을 하려고

aamoos.tistory.com

 

목표

- 저번장까지 파일업로드를 개발하였는데, 이번장에서는 등록한 게시판, 파일테이블을 이어주는 중간 테이블을 만들어보겠습니다.

 

BoardFile.java

package jpa.board.entity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;

/**
 * packageName    : jpa.board.entity
 * fileName       : BoardFile
 * author         : 김재성
 * date           : 2022-08-09
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-09        김재성       최초 생성
 */

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public class BoardFile {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_file_id")
    private Long id;            //번호

    private Long boardId;
    private String delYn;

    @OneToOne
    @JoinColumn(name = "file_id")
    private File file;

    @Builder
    public BoardFile(Long boardId, Long fileId, String delYn, File file){
        this.boardId = boardId;
        this.delYn = "N";
        this.file = file;
    }
}
- File과 OneToOne 연관관계를 추가하였습니다.

 

File.java

package jpa.board.entity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * packageName    : jpa.board.entity
 * fileName       : File
 * author         : 김재성
 * date           : 2022-08-05
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-05        김재성       최초 생성
 */
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public class File {

    @Id
    @GeneratedValue
    @Column(name = "file_id")
    private Long id;                    //id

    @Column(nullable = false)
    private String originFileName;      //원본 파일명

    @Column(nullable = false)
    private String savedFileName;       //저장된 파일명

    private String uploadDir;           //경로명

    private String extension;           //확장자

    private Long size;                  //파일 사이즈

    private String contentType;         //ContentType

    @CreatedDate
    private LocalDateTime regDate;     //등록 날짜

    @OneToOne(mappedBy = "file")
    private BoardFile boardFile;

    @Builder
    public File(Long id, String originFileName, String savedFileName
            , String uploadDir, String extension, Long size, String contentType){
        this.id = id;
        this.originFileName = originFileName;
        this.savedFileName = savedFileName;
        this.uploadDir = uploadDir;
        this.extension = extension;
        this.size = size;
        this.contentType = contentType;
    }
}
- BoardFile과 OneToOne 연관관계를 추가하였습니다.

 

 

BoardFileDto.java

package jpa.board.dto;

import com.querydsl.core.annotations.QueryProjection;
import jpa.board.entity.Board;
import jpa.board.entity.BoardFile;
import jpa.board.entity.File;
import lombok.Builder;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * packageName    : jpa.board.dto
 * fileName       : BoardFileDto
 * author         : 김재성
 * date           : 2022-08-09
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-09        김재성       최초 생성
 */

@Data
public class BoardFileDto {

    private Long id;

    private Long boardId;

    public BoardFileDto(){

    }

    @Builder
    public BoardFileDto(Long boardId){
        this.boardId = boardId;
    }

    public BoardFile toEntity(File file){
        return BoardFile.builder()
                .boardId(boardId)
                .file(file)
                .build();
    }
}
- entity로 바꿔줄때 file을 받는 부분을 추가하였습니다.

 

FileService.java

package jpa.board.service;

import jpa.board.dto.BoardDto;
import jpa.board.dto.BoardFileDto;
import jpa.board.dto.FileDto;
import jpa.board.entity.BoardFile;
import jpa.board.entity.Member;
import jpa.board.repository.BoardFileRepository;
import jpa.board.repository.FileRepository;
import jpa.board.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.InputStream;
import java.util.*;

/**
 * packageName    : jpa.board.service
 * fileName       : FileService
 * author         : 김재성
 * date           : 2022-08-05
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-05        김재성       최초 생성
 */

@Service
@RequiredArgsConstructor
@Slf4j
public class FileService {

    @Value("${upload.path}")
    private String uploadDir;

    private final FileRepository fileRepository;

    private final BoardFileRepository boardFileRepository;

    private final MemberRepository memberRepository;

    @Transactional
    public Map<String, Object> saveFile(BoardDto boardDto, Long boardId) throws Exception {
        List<MultipartFile> multipartFile = boardDto.getMultipartFile();

        //결과 Map
        Map<String, Object> result = new HashMap<String, Object>();

        //파일 시퀀스 리스트
        List<Long> fileIds = new ArrayList<Long>();

        try {
            if (multipartFile != null) {
                if (multipartFile.size() > 0 && !multipartFile.get(0).getOriginalFilename().equals("")) {
                    for (MultipartFile file1 : multipartFile) {
                        String originalFileName = file1.getOriginalFilename();    //오리지날 파일명
                        String extension = originalFileName.substring(originalFileName.lastIndexOf("."));    //파일 확장자
                        String savedFileName = UUID.randomUUID() + extension;    //저장될 파일 명

                        File targetFile = new File(uploadDir + savedFileName);

                        //초기값으로 fail 설정
                        result.put("result", "FAIL");

                        FileDto fileDto = FileDto.builder()
                                .originFileName(originalFileName)
                                .savedFileName(savedFileName)
                                .uploadDir(uploadDir)
                                .extension(extension)
                                .size(file1.getSize())
                                .contentType(file1.getContentType())
                                .build();
                        //파일 insert
                        jpa.board.entity.File file = fileDto.toEntity();
                        Long fileId = insertFile(file);
                        log.info("fileId={}", fileId);

                        try {
                            InputStream fileStream = file1.getInputStream();
                            FileUtils.copyInputStreamToFile(fileStream, targetFile); //파일 저장
                            //배열에 담기
                            fileIds.add(fileId);
                            result.put("fileIdxs", fileIds.toString());
                            result.put("result", "OK");
                        } catch (Exception e) {
                            //파일삭제
                            FileUtils.deleteQuietly(targetFile);    //저장된 현재 파일 삭제
                            e.printStackTrace();
                            result.put("result", "FAIL");
                            break;
                        }

                        BoardFileDto boardFileDto = BoardFileDto.builder()
                                .boardId(boardId)
                                .build();

                        BoardFile boardFile = boardFileDto.toEntity(file);
                        insertBoardFile(boardFile);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /** 파일 저장 db */
    @Transactional
    public Long insertFile(jpa.board.entity.File file) {
        return fileRepository.save(file).getId();
    }

    @Transactional
    public Long insertBoardFile(BoardFile boardFile) {
        return boardFileRepository.save(boardFile).getId();
    }

}
- insertBoardFile 메소드를 추가하였습니다. boardFile은 id만 받고, 삭제여부는 N으로 고정하였습니다. boardFileDto를 entity로 변환할때 전에 저장한 file entity를 설정해주고 boardFile을 저장하는 방식으로 개발하였습니다.

 

BoardFileRepository.java

package jpa.board.repository;

import jpa.board.entity.Board;
import jpa.board.entity.BoardFile;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * packageName    : jpa.board.repository
 * fileName       : BoardFileRepository
 * author         : 김재성
 * date           : 2022-08-09
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-09        김재성       최초 생성
 */
public interface BoardFileRepository extends JpaRepository<BoardFile, Long> {
}

 

결과화면

 

게시판 테이블

 

파일 테이블

 

게시판 첨부파일 테이블

 

파일

 

- 게시판 등록을 해보면 테이블 3개로 나뉘어 등록이 되는것을 볼수있습니다. BOARD_FILE 테이블을 통해 1번 게시물에 2번 3번 파일이 등록된것을 알수 있습니다. 다음장에서는 등록한 파일을 상세에서 다운로드 할수 있는 기능을 개발해보겠습니다.

 

다음글 보기

https://aamoos.tistory.com/688

 

복사했습니다!