스프링 부트 환경에서 바이너리 형식 파일을 업로드 하기 💾
Springboot 3.4.0, JDK 21SE, Spring data JPA, mariaDB, lombok, modelmapper
<form> 사용을 통한 파일 업로드 예제MultipartFile 이란 SpringFramework에서 제공하는 파일 업로드용 인터페이스이다.
보통 HTML 환경에서 유저의 Multipart 형식의 데이터를 DB 에 저장을 할때 사용한다.
SpringFramework 내 Spring Web 모듈에서 제공하는 인터페이스로 지금부터 사용법을 알아보자!
MIME 타입 중 multipart/form-data 타입을 사용하여
한 HTTP 요청 안에서 여러 가지 데이터를 분리된 "part"로 전송하는 방법이다.
예를 들어 텍스트 데이터와 바이너리 형식의 데이터들(이미지 파일 등)을 하나의 요청으로 보낼때 사용한다.
아파치 톰캣 폴더 내의 web.xml 파일 내 정어 있는 밈타입들을 확인해보면 이해하기 쉬움
MultipartFile에서 제공하는 Method들을 알아보자.
1. bolean isEmpty()
- 파일이 비어있는지 확인하여 boolean 타입으로 반환합니다.
2. byte[] getBytes()
- 파일의 내용을 byte형식의 배열로 반환합니다.
3. String getContentType()
- 파일의 콘텐츠 유형을 MIME_(인터넷에 전달되는 파일 포맷 및 식별자)_타입으로 반환
- type/subtype 형식으로 반환 ex) application/json, image/png
4. InputStream getInputStream()
- 파일 내용을 읽기 위해 InputStream을 반환(read, available등의 메소드 사용이 필요 할 때)
5. String getName()
- 필드의 이름을 반환(예: <input id="1" name="potatoImg">의 name 부분)
6. String getOriginalFilename()✨
- 파일의 원본 이름(클라이언트 파일 시스템 기준)을 반환
7. Long getSize()
- 파일을 바이트 단위로 반환
8. void transferTo(File dest 혹은 Path dest)✨
- 현재 MultipartFile에서 사용중인 임시 저장소에 있는 해당 파일을 지정된 dest 경로로 이동합니다.
<form> 방식스프링 부트 환경에서 MultipartFile을 사용하여 form/post 방식으로 DB, Server local 스토리지에 파일을 저장해보자💾
### html 부분
<form action="/potato/register" method="post" enctype="multipart/form-data">
<p>
감자 이름 : <input type="text" name="potatoName">
</p>
<p>
감자 개월수 : <input type="number" name="potatoAge">
</p>
<p>
감자 사진 : <input type="file" name="potatoImg">
</p>
<button type="submit">제출</button>
</form>
html을 살펴보면
<form>태그 내 enctype 속성을 꼭 "multipart/form-data"로 설정하여 사용하여야 한다.
만약 속성을 설정해주지 않을 경우 default로 application/x-www-form-urlencoded가 설정되어 바이너리 타입의 데이터를 수신하지 못하게 된다!
<input type="file">로 input을 추가하여
submit 시 컨트롤러로 문제 없이 파일이 갈 수 있도록 작성해준다.
@PostMapping("/register")
public String register(MultipartFile potatoImg, PotatoDTO potatoDTO){
potatoService.imgRegister(potatoImg,potatoService.register(potatoDTO));
// 글 등록 후 새로 생성된 글의 pk값을 리턴하여 해당 값을 상속받는 potatoImg에 전달
return "/potato/success";
}
컨트롤러 부분은 위와 같이 MultipartFile 인터페이스를 import✨하여 file 객체를 하나 파라미터로 받아주며 이외에 multipart/form-data로 함께 넘어온 나머지 값들은 DTO로 받아준다.
추가적으로 예제엔 imgRegister의 파라미터로 파일과 register의 리턴값인 Long을 같이 넣어주어 한줄로 작성했지만 가독성 및 유지보수를 위해 나눠서 관리 하시길 권장 드린다
@Value("C:\\images")
private String uploadPath;
@Override
public void imgRegister(MultipartFile potatoImg, Long kno){
// register method에서 리턴 받은 게시글(potato)의 키값을 받아 넣음
try {
String afterName = fileUplaod(potatoImg);
// fileupload 메소드의 리턴값으로 UUID가 합쳐진 이름을 받아 풀 네임을 변수에 담는다
Potato potatoEntity = potatoRepository.findById(kno).get();
// Join 컬럼에 값을 대입해주기 위해
PotatoImgDTO potatoImgDTO =
PotatoImgDTO.builder()
.url(uploadPath+"/"+afterName)
.afterName(afterName)
.originalName(potatoImg.getOriginalFilename())
.potato(potatoEntity).build();
//빌더 패턴을 통해 각 값들을 세팅
potatoImgRepository.save(modelMapper.map(potatoImgDTO, potatoImg.class));
}
catch (IOException ioException){
System.out.println("파일 저장 오류");
}
}
DB에 파일을 저장하는 메소드로 Bulider 패턴을 통해 각각 필요한 컬럼 값들을 세팅해준다.
그 중 눈여겨 볼 내용은 .getOriginalFilename()✨으로 원본 이름을 가져와 저장하고
중복값 방지 등을 위한 방법으로 UUID를 앞에 붙혀 afterName도 붙혀준다.
url 부분의 파라미터 uploadPath는 서비스 상단에 경로를 변수에 담아 관리해주면 편하다.
※ 추가적으로 IE, Edge의 경우 파일이 경로명까지 붙어 들어오는 경우가 있어 lastIndex로 잘라서 이름을 겟하는 내용이 필요하다.
@Override
public String fileUplaod(MultipartFile file) throws IOException {
UUID uuid = UUID.randomUUID(); // java.util의 UUID를 이용하여 랜덤 UUID를 파일명 앞에 붙혀준다.
String afterName = uuid.toString()+file.getOriginalFilename();
// 파일 최종 이름에 랜덤 UUID를 붙혀 중복성을 제거하여 관리한다.
String fileUploadUrl = uploadPath + "/" + afterName;
// 업로드할 위치에 디렉토리 path+/+파일 이름을 넣어준다.
File uploadFile = new File(fileUploadUrl);
// 업로드할 파일을 만들어준다.
try {
file.transferTo(uploadFile);
// MultipartFile을 해당 업로드할 파일 경로로 옮긴다.
} catch (IOException ioException) {
System.out.println("IOException 발생 : " + ioException.getMessage());
}//IO예외처리
return afterName; // 체인메소드 형식으로 위에서 활용하고자 리턴값 줌
}
Local 경로에 실제 파일을 저장 하려면 위에서 배운 transferTo를 사용 할 것이다.
Filecopy 혹은 fileoutstream등 방법은 많지만 transferTo메소드를 사용하면 간편히 저장 할 수 있다.
위에서 파일 경로와 UUID를 붙힌 이름을 받아 새로운 File을 만들어주고
transferTo 파라미터✨로 넣어주면 끝이다!
스프링 부트 환경에서 MultipartFile을 사용하여 JQuery Ajax 방식으로 DB, Server local 스토리지에 파일을 저장해보자💾
Controller 이후 데이터 DB 저장, 파일 저장 같은 경우 같은 경우 같은 로직으로 처리되기 때문에
생략 후 HTML과 Script, Controller 부분만 다룰 예정이다.
<div class = 'uploadDiv'>
<input id="uploadFile" type="file" name="uploadFile">
</div>
<button id="uploadBtn">UPLOAD</button>
<script>
$("#uploadBtn").on("click",function(e){
const uploadFile = $("#uploadFile")[0].files[0];
const formData = new FormData()
formData.append("uploadFile",uploadFile)
console.log(formData)
$.ajax({
url:'/potato/upload',
processData: false,
contentType : false,
enctype :"multipart/form-data"✨,
data : formData,
type : 'POST',
success : function(result){
alert("Good uploaded")
location.reload()
},
error : function (){
alert("failed, go work")
location.reload()
}
})
})
</script>
위 form을 사용하는 예제와 크게 다를게 없으나 Ajax로 보낼 경우
$(선택자)[0].files[0] 와 같이 JQuery 객체를 DOM 객체로 변환하여 담아야 함.
ajax 속성 설정값 url controller 부분의 포스트 맵핑 부분과 requestmapping을 고려하여 맵핑 processData true일 경우 jQuery에서 데이터를 쿼리 문자열로 변환하므로 false로 설정 contentType true일 경우 application/x-www-form-ulencoded로 인코딩하므로 false로 설정 enctype form과 같이 multipart/form-data✨ data 서버로 보낼 FormData 타입으로 생성한 객체
@ResponseBody
@PostMapping("/upload")
public void upload(@RequestParam("uploadFile") MultipartFile uploadFile){
log.info(uploadFile.getOriginalFilename());
}
같은 로직을 가지기에 log4j2로 들어오는지만 찍어 보았다.
@restconroller를 사용 할 경우 @ResponseBody는 생략 가능하나 일반 Controller이기에 사용
@ReuqestParam을 통해 맵핑하여 파일 수신 완료
MultipartFile을 이용하여 MVC 패턴 중 뷰에서 모델로 파일을 보내는 방법을 알아보았다.
유의 해야 할 점은 form으로 보낼 경우 encType 설정이 잘 되었는지 확인,
Ajax를 통해 보낼 경우 각 종 설정값이 잘 설정되었는지 확인이 필요한 것 같다.
파일이름 중복 등의 예외사항 및 파일 관리를 고려하여
날짜별 로컬 서버 dir 관리 등의 방법도 필요 할 것으로 보인다.
다음장(CRUD 중 RUD 작성 예정)에서 만나요😁
큰 도움이 되는 정보 감사합니다~ 굿~!