1. 파일 업로드
- 업로드: 1) 일반 업로드: form 형식, 2) 비동기 업로드 AJAX(드래그 하면 바로 올라가는 거)
- 저장: 1) 데이터 베이스 2) 하드 디스크 3) 클라우드
1) 일반 업로드
- 4번째 사진 참고 사항
- 파일 업로드에서는 enctype(인코딩 타입)을 multipart/form-data 반드시 써줄 것!
- 파일 type은 file
- 아래는 컨트롤러
- trasferTo: 파일 업로드를 진행해줍니다.
- 일반 업로드 실습
1] 단일
2] 단일 태그로 다중 파일 업로드
3] 복수 태그로 여러 파일 업로드: list에서 빈 값 제거 넣을 것!
list.stream().filter( (x) -> x.isEmpty() == false ).collect(Collectors.toList());
2) 비동기 업로드
- JSON 형식으로 보낼 때는 content 타입이 application/json이었음
- XML로 보낼 때는 application/xml
- 일반 폼 형식: form 데이터를 전송 할 때는 enctype=enctype="multipart/form-data"
- 파일 전송 형태의 폼 형식: multipart/formdata
- 서버에서 값을 받는 것은 동일한데 responsebody 달아줘야 성공 실패 여부를 알 수 있음
* application.properties, UploadController, ex01, ex01_ok
- application.properties
########## 파일 업로드 관련 내용 설정 #############
# 파일 업로드 가능 여부 설정
spring.servlet.multipart.enabled=true
# 한번에 최대 업로드 가능한 용량
spring.servlet.multipart.max-request-size=50MB
# 파일 하나당 최대 크기
spring.servlet.multipart.max-file-size=10MB
# 실제 저장 경로(업로드 경로)
project.uploadpath=C:\\Users\\user\\Desktop\\minjeong\\program\\course\\springboot\\upload
- UploadController
package com.simple.basic.controller;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import net.coobird.thumbnailator.Thumbnailator;
import oracle.jdbc.proxy.annotation.Post;
@Controller
@RequestMapping("/upload")
public class UploadController {
@GetMapping("/ex01")
public void ex01() {}
@Value("${project.uploadpath}")
private String uploadpath;
//날짜 별로 폴더 생성
public String makeDir() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
String now = sdf.format(date);
String path = uploadpath + "\\" + now; //경로
File file = new File(path);
if(file.exists() == false ) { //존재 하면 true
file.mkdir(); //폴더 생성
}
return path;
}
//단일 파일 업로드
@PostMapping("/upload_ok")
public String uploadOk(@RequestParam("file") MultipartFile file) {
// String origin = file.getOriginalFilename(); //파일명
// long size = file.getSize(); //사이즈
// String type = file.getContentType(); //파일 데이터에 컨텐츠 타입
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = filepath + "\\" + uuid + "_" + filename;
System.out.println(filename);
System.out.println(filepath);
System.out.println(uuid);
System.out.println(savename);
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
//썸네일 경로
String thumbsname = filepath + "\\" + uuid + "_thumbs_" + filename;
//썸네일 생성(복사할 파일 위치, 썸네일 생성 위, 가로, 세로)
Thumbnailator.createThumbnail(new File(savename),
new File(thumbsname),
150,
150);
} catch (IOException e) {
e.printStackTrace();
}
return "upload/ex01_ok";
}
//multiple 옵션으로 다중파일 업로드
@PostMapping("/upload_ok2")
public String uploadOk2(MultipartHttpServletRequest files) {
//name 태그가 file인 것을 찾음
List<MultipartFile> list = files.getFiles("file");
//반복 처리
for(MultipartFile file : list) {
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = filepath + "\\" + uuid + "_" + filename;
System.out.println(filename);
System.out.println(filepath);
System.out.println(uuid);
System.out.println(savename);
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload/ex01_ok";
}
//복수 태그로 여러 파일 업로드
@PostMapping("/upload_ok3")
public String uploadOk3(@RequestParam("file") List<MultipartFile> list) {
//리스트에서 빈 값은 제거
list = list.stream().filter( (x) -> x.isEmpty() == false ).collect(Collectors.toList());
//반복 처리
for(MultipartFile file : list) {
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = filepath + "\\" + uuid + "_" + filename;
System.out.println(filename);
System.out.println(filepath);
System.out.println(uuid);
System.out.println(savename);
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload/ex01_ok";
}
//비동기 업로드 - 넘어오는 데이터는 form 형식이기 때문에 vo or requestParam으로 받으면 됩니다.
@PostMapping("/upload_ok4")
@ResponseBody //return 값이 요청이 온 곳으로 반환
public String uploadOk4(@RequestParam("file") MultipartFile file,
@RequestParam("writer") String writer) {
System.out.println(file);
System.out.println(writer);
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = filepath + "\\" + uuid + "_" + filename;
System.out.println(filename);
System.out.println(filepath);
System.out.println(uuid);
System.out.println(savename);
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
}
- ex01
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>업로드 예제</h3>
<!-- 단일파일 업로드 -->
<form action="upload_ok" method="post" enctype="multipart/form-data" >
<input type="file" name="file"><br/>
<input type="submit" value="업로드"><br/>
</form>
<hr/>
<h3>multiple옵션으로 다중파일 업로드</h3>
<form action="upload_ok2" method="post" enctype="multipart/form-data" >
<input type="file" name="file" multiple="multiple"><br/>
<input type="submit" value="업로드"><br/>
</form>
<hr/>
<h3>복수태그로 여러파일 업로드</h3>
<form action="upload_ok3" method="post" enctype="multipart/form-data" >
<input type="file" name="file"><br/>
<input type="file" name="file"><br/>
<input type="file" name="file"><br/>
<input type="submit" value="업로드"><br/>
</form>
<hr/>
<h3>비동기형식의 업로드</h3>
<div>
<input type="file" name="file" id="a"><br/>
<input type="text" name="writer" id="writer"><br/>
<input type="button" value="업로드" id="btn"><br/>
</div>
<script src="https://code.jquery.com/jquery-3.6.3.js"></script>
<script>
$("#btn").click(function() {
//파일데이터 추출
var file = $("#a");
console.log(file[0]); //순수한태그
console.dir(file[0].files[0]); //파일데이터
//사용자가 입력 text값
var writer = $("#writer").val();
//폼태그로 추가
var formData = new FormData(); //폼객체
formData.append("file", file[0].files[0]); //name, 값
formData.append("writer", writer); //name, 값
$.ajax({
url : "upload_ok4",
type : "post",
data : formData, //보내는데이터 form
contentType: false, //보내는데이터타입 false -> "multipart/form-data" 로 선언됩니다
processData : false, //폼데이터가 name=값&name=값 형식으로 자동변경되는 것을 막아줍니다.
success: function(result) { //콜백
if(result == "success") {
alert("업로드가 완료되었습니다");
}
},
error: function(err) {
alert("업로드 에러발생");
}
})
})
</script>
</body>
</html>
- ex01_ok
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>결과 페이지</h3>
<a href="ex01">다시 올리기</a>
</body>
</html>
2. 일반 업로드를 이용하여 상품 이미지 구현
- 메이븐 레파지토리
* jpg, png, jpeg 형식이 아니면 등록되지 않게끔 구현
- bootMyweb 프로젝트 열어서 build.gradle에 추가
//sql 실행 로그 기능
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
//이미지 썸네일
implementation 'net.coobird:thumbnailator:0.4.8'
- application.property에 추가
########## 파일 업로드 관련 내용 설정 #############
# 파일 업로드 가능 여부 설정
spring.servlet.multipart.enabled=true
# 한번에 최대 업로드 가능한 용량
spring.servlet.multipart.max-request-size=50MB
# 파일 하나당 최대 크기
spring.servlet.multipart.max-file-size=10MB
# 실제 저장 경로(업로드 경로)
project.uploadpath=C:\\Users\\user\\Desktop\\minjeong\\program\\course\\springboot\\upload
- productController
//파일 업로드 작업 →
//리스트에서 빈 값은 제거
list = list.stream()
.filter( (x) -> x.isEmpty() == false )
.collect(Collectors.toList());
//확장자가 image라면 경고문
for(MultipartFile file : list) {
if( file.getContentType().contains("image") == false ) {
ra.addFlashAttribute("msg", "png, jpg, jpeg 형식만 등록 가능합니다.");
return "redirect:/product/productReg";
}
}
- productReg 맨 마지막에 alert 추가, enctype 추가
<script th:inline="javascript">
var msg = JSON.parse('[[${msg}]]');
if (msg != null) {
alert(msg);
}
</script>
* 파일 업로드 작업을 → service영역으로 위임
1) 서비스에 위임
2) 테이블 생성
3) serviceimpl에 추가
//업로드 패스
@Value("${project.uploadpath}")
private String uploadpath;
//날짜 별로 폴더 생성
public String makeDir() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
String now = sdf.format(date);
String path = uploadpath + "\\" + now; //경로
File file = new File(path);
if(file.exists() == false ) { //존재하면 true
file.mkdir(); //폴더생성
}
return path;
}
//글 등록
@Override
public int regist(ProductVO vo, List<MultipartFile> list) {
//1. 글 등록 처리 ->
int result = productMapper.regist(vo);
//2. 파일 인서트 ->
//반복 처리
for(MultipartFile file : list) {
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = filepath + "\\" + uuid + "_" + filename;
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
} catch (IOException e) {
e.printStackTrace();
}
//인서트
} //end for
return ;
}
4) mapper.java에 추가
5) productUploadVO에 추가
package com.coding404.myweb.command;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductUploadVO {
/*
CREATE TABLE PRODUCT_UPLOAD (
UPLOAD_NO INT PRIMARY KEY auto_increment,
FILENAME varchar(100) not null, ##실제파일명
FILEPATH varchar(100) not null, ##폴더명
UUID varchar(50) not null, ##UUID명
REGDATE TIMESTAMP default now(),
PROD_ID INT, ##FK
PROD_WRITER VARCHAR(20) ##FK
);
*/
private int upload_no;
private String filename;
private String filepath;
private String uuid;
private LocalDateTime regdate;
private int prod_id;
private String prod_writer;
}
5) mapper.xml에 추가
<insert id="registFile" parameterType="ProductUploadVO">
insert into PRODUCT_UPLOAD(filename,
filepath,
uuid,
prod_id,
prod_writer)
values(#{filename},
#{filepath},
#{uuid},
#{prod_id}
#{prod_writer})
</insert>
6) productServiceImpl 위에 인서트 부분 수정
ProductUploadVO prodVO = ProductUploadVO.builder()
.filename(filename)
.filepath(filepath)
.uuid(uuid)
.prod_id(??) //?
.prod_writer(vo.getProd_writer()) //
.bulid();
productMapper.registFile(prodVO);
- 오라클에서는 시퀀스 쓰면 id 부분이 처리가 가능한데, mySQL에서는 안됨
7) 그래서 select 처리 인서트 위에 해줘야 함(멀티 insert 상황에서 pk 값 얻어오기)
(인서트-insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리)
<!--
1. insert 전에 product 테이블의 키 값을 selectKey 태그를 이용해서 얻습니다.
2. resultType은 조회된 결과 타입,
KeyProperty은 sql에 전달되는 vo의 저장할 key 값
order는 BEFORE, AFTER = 인서트 이전에 실행 or, 인서트 이후에 실행
-->
<insert id="registFile" parameterType="ProductUploadVO">
<selectKey resultType="int" keyProperty="prod_id" order="BEFORE">
select max(prod_id) as prod_id from PRODUCT where prod_writer = #{prod_writer}
</selectKey>
insert into PRODUCT_UPLOAD(filename,
filepath,
uuid,
prod_id,
prod_writer)
values(#{filename},
#{filepath},
#{uuid},
#{prod_id}
#{prod_writer})
</insert>
8) serviceImpl 수정
//글 등록
//한 프로세스 안에서 예외가 발생하면 기존에 진행했던 CRUD 작업을 rollback 시킵니다.
//조건 - catch를 통해서 예외 처리가 진행 되면 트랜잭션 처리가 되지 않습니다.
@Override
@Transactional(rollbackFor = Exception.class)
//exception 터지면 실행했던 sql rollback 해줌(insert가 여러번 들어갈 때 넣어주기 좋음
//스프링은 따로 설정해줘야 하지만 스프링 부트는 바로 사용이 가능)
//예외를 잡을 수가 없음
public int regist(ProductVO vo, List<MultipartFile> list) {
//1. 글 등록 처리 ->
int result = productMapper.regist(vo);
//2. 파일 인서트 ->
//반복 처리
for(MultipartFile file : list) {
//파일 명
String origin = file.getOriginalFilename();
//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
String filename = origin.substring( origin.lastIndexOf("\\") + 1 );
//폴더 생성
String filepath = makeDir();
//중복 파일의 처리
String uuid = UUID.randomUUID().toString();
//최종 저장 경로
String savename = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;
try {
File save = new File(savename); //세이브 경로
file.transferTo(save);
} catch (IOException e) {
e.printStackTrace();
return 0; //실패의 의미로
}
//인서트-insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리
ProductUploadVO prodVO = ProductUploadVO.builder()
.filename(filename)
.filepath(filepath)
.uuid(uuid)
.prod_writer(vo.getProd_writer()) //
.build();
productMapper.registFile(prodVO);
} //end for
return result; //성공시 1, 실패시 0
}
'TIL > Spring boot' 카테고리의 다른 글
day92-spring boot (0) | 2023.02.23 |
---|---|
day91-spring boot (0) | 2023.02.22 |
day88-spring boot (0) | 2023.02.17 |
day86-spring boot (0) | 2023.02.16 |
day85-spring boot (0) | 2023.02.15 |