Published 2022. 8. 9. 16:10
이전글 보기
https://aamoos.tistory.com/683
목표
- 파일을 여러개 올릴수 있는 input을 추가한후 멀티파일 업로드 기능을 구현하려고 합니다. 이번장에서는 파일 업로드와, File 테이블에 업로드한 파일의 정보만 쌓는 부분만 개발을 해보겠습니다.
소스
write.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/default_layout}">
<head layout:fragment="css">
<style>
.fieldError {
border-color: #bd2130;
}
.form-group p{
color: red;
}
</style>
</head>
<div layout:fragment="content" class="content">
<form th:action th:object="${boardDto}" method="post" enctype="multipart/form-data">
<article>
<div class="container" role="main">
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" name="title" placeholder="제목을 입력해 주세요" th:class="${#fields.hasErrors('title')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Incorrect date</p>
</div>
<br>
<div class="mb-3">
<label for="reg_id">작성자</label>
<input type="text" class="form-control" id="reg_id" name="regId" value="관리자" readonly>
</div>
<br>
<div class="mb-3">
<label for="content">내용</label>
<textarea class="form-control" rows="5" id="content" name="content" placeholder="내용을 입력해 주세요"></textarea>
</div>
<br>
<div class="mb-3">
<label for="formFileMultiple" class="form-label">파일업로드</label>
<input class="form-control" type="file" id="formFileMultiple" name="multipartFile" multiple>
</div>
<br>
<div>
<button type="submit" class="btn btn-sm btn-primary" id="btnSave">저장</button>
<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
</div>
</div>
</article>
</form>
</div>
</html>
<script>
</script>
- 파일 업로드 영역 을 추가하였습니다
화면
BoardService.java
@Transactional
public Long saveBoard(BoardDto boardDto) throws Exception {
List<Member> memberList = memberRepository.findAll();
Member member = memberList.get(0);
Board board = null;
//insert
if(boardDto.getId() == null){
board = boardDto.toEntity(member);
boardRepository.save(board);
}
//update
else{
board = boardRepository.findById(boardDto.getId()).get();
board.update(boardDto.getTitle(), boardDto.getContent());
}
//파일 저장
fileService.saveFile(boardDto);
return board.getId();
}
- 기존 코드에서는 게시글 저장할때 파일을 저장하지 않았습니다. FileService를 하나 생성후 saveFile을 호출하도록 추가합니다.
FileService.java
package jpa.board.service;
import jpa.board.dto.BoardDto;
import jpa.board.dto.FileDto;
import jpa.board.repository.FileRepository;
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;
@Transactional
public Map<String, Object> saveFile(BoardDto boardDto) 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
Long fileId = insertFile(fileDto.toEntity());
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;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/** 파일 저장 db */
@Transactional
public Long insertFile(jpa.board.entity.File file) {
return fileRepository.save(file).getId();
}
}
- List<MultipartFile>로 file들을 받아서 for문으로 돌려서 해당 경로에 파일을 저장하고 db에 쌓는 방식으로 개발하였습니다.
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; //등록 날짜
@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;
}
}
FileDto.java
package jpa.board.dto;
import jpa.board.entity.Board;
import jpa.board.entity.File;
import jpa.board.entity.Member;
import lombok.Builder;
import lombok.Data;
import javax.persistence.Column;
/**
* packageName : jpa.board.dto
* fileName : FileDto
* author : 김재성
* date : 2022-08-05
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022-08-05 김재성 최초 생성
*/
@Data
public class FileDto {
private Long id; //id
private String originFileName; //원본 파일명
private String savedFileName; //저장된 파일명
private String uploadDir; //경로명
private String extension; //확장자
private Long size; //파일 사이즈
private String contentType; //ContentType
public FileDto(){
}
@Builder
public FileDto(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;
}
public File toEntity(){
return File.builder()
.originFileName(originFileName)
.savedFileName(savedFileName)
.uploadDir(uploadDir)
.extension(extension)
.size(size)
.contentType(contentType)
.build();
}
}
application.yml
spring: #띄어쓰기 없음
datasource: #띄어쓰기 2칸
url: jdbc:h2:tcp://localhost/~/board #4칸
username: sa
password:
driver-class-name: org.h2.Driver
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
jpa: #띄어쓰기 2칸
hibernate: #띄어쓰기 4칸
ddl-auto: create #띄어쓰기 6칸
properties: #띄어쓰기 4칸
hibernate: #띄어쓰기 6칸
# show_sql: true #띄어쓰기 8칸
format_sql: true #띄어쓰기 8칸
mvc:
hidden-method:
filter:
enabled: true
devtools:
livereload:
enabled: true
thymeleaf:
cache: false
restart:
enable: false #운영에서는 제거
logging:
level:
org.hibernate.SQL: debug
upload:
path: C:/study/file/
- multipart 파일 업로드시 max size를 설정하는 부분을 추가하였습니다.
결과화면
다음장에서 게시판 파일 정보 테이블을 만들면서 File 테이블과 연관관계를 개발 해보겠습니다.