파일 업로드 테이블 생성
CREATE TABLE "UPLOAD_FILE"(
FILE_NO NUMBER PRIMARY KEY,
FILE_PATH VARCHAR2(500) NOT NULL,
FILE_ORIGINAL_NAME VARCHAR2(300) NOT NULL,
FILE_RENAME VARCHAR2(100) NOT NULL,
FILE_UPLOAD_DATE DATE DEFAULT SYSDATE,
MEMBER_NO NUMBER REFERENCES "MEMBER"
);
COMMENT ON COLUMN "UPLOAD_FILE".FILE_NO IS '파일 번호(PK)';
COMMENT ON COLUMN "UPLOAD_FILE".FILE_PATH IS '클라이언트 요청 경로';
COMMENT ON COLUMN "UPLOAD_FILE".FILE_ORIGINAL_NAME IS '파일 원본명';
COMMENT ON COLUMN "UPLOAD_FILE".FILE_RENAME IS '변경된 파일';
COMMENT ON COLUMN "UPLOAD_FILE".FILE_UPLOAD_DATE IS '업로드 날짜';
COMMENT ON COLUMN "UPLOAD_FILE".MEMBER_NO IS 'MEMBER 테이블의 PK(MEMBER_NO) 참조';
CREATE SEQUENCE SEQ_FILE_NO NOCACHE;
controller 에서 페이지 이동 연결
@GetMapping("fileTest")
public String fileTest() {
return "myPage/myPage-fileTest";
}
enctype 이란??
GET 요청은 보안 문제, 파일명에 길이 제한 있어서 POST 권장
myPage-fileTest.html
<form action="/myPage/file/test1"
method="POST"
enctype="multipart/form-data"
>
<h3>업로드 테스트 1</h3>
<!-- type="file" 도 결국에는 Parameter
-> @RequestParam으로 처리 가능
-->
<input type="file" name="uploadFile">
<button class="myPage-submit">제출하기</button>
</form>
제출하기 버튼 눌렀을 때 받아줄 Controller 필요
이를 MultipartResolver를 이용해서 섞여있는 파라미터를 분리
문자열, 숫자 -> String
파일 -> MultipartFile
파일을 디스크에 쓸 때까지의 임계값 메모리에 대한 임계값
기본값 : 0B (Byte)
50MB 줄거임 == 52,428,800 Bytes
-> 업로드되는 파일의 크기 50MB까지는 메모리에 저장하다가
초과시 디스크에 저장하겠다.(HDD, SSD)
threshold 문턱 한계
spring.servlet.multipart.file-size-threshold=52428800
HTTP 요청당 최대 크기 클라이언트가 한번 요청할 때
기본값 : 10MB
50MB == 52,428,800
spring.servlet.multipart.max-file-size=10485760
파일의 임시 저장 경로 변경
\AppData\Local\Temp\tomcat.80.23242213535e134235252\work\Tomcat...임시파일
(AppData 는 숨겨진 폴더 엄청 깊이 숨어있음)
기본값이 톰캣 기본 폴더로 저장경로가 지정되어있다.
찾아다가 확인할 수가 없어서 임시 저장 경로를 바꿔줌
spring.servlet.multipart.location=C:\\uploadFiles\\temp\\
(백슬래쉬는 2개 그냥 슬래쉬는 한개만 써도 됨) 폴더를 만들어주는 게 아니라 폴더를 직접 생성해둬야함 폴더 경로 찾아서 저장해주는 거 없으면 오류남
FileConfig
@Configuration
@PropertySource("classpath:/config.properties")
public class FileConfig {
// 파일 업로드 임계값
@Value("${spring.servlet.multipart.file-size-threshold}")
private long fileSizeThreshold;
// 요청당 파일 최대 크기
@Value("${spring.servlet.multipart.max-request-size}")
private long maxRequestSize;
// 개별 파일당 최대 크기
@Value("${spring.servlet.multipart.max-file-size}")
private long maxFileSize;
// 임계값 초과 시 임시 저장 폴더 경로
@Value("${spring.servlet.multipart.location}")
private String location;
/* MultipartResolver 설정 */
@Bean
public MultipartConfigElement configElement() {
// MultipartConfigElement :
// 파일 업로드를 처리하는데 사용되는 MultipartConfigElement를 구성하고 반환
// 파일 업로드를 위한 구성 옵션을 설정하는데 사용
// 업로드 파일의 최대 크기, 메모리에서의 임시 저장 경로 등을 설정 가능
// -> 서버 경로 작성 (보안 문제) => config.properties 에 작성 후 가져와서 사용
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setFileSizeThreshold(DataSize.ofBytes(fileSizeThreshold));
factory.setMaxFileSize(DataSize.ofBytes(maxFileSize));
factory.setMaxRequestSize(DataSize.ofBytes(maxRequestSize));
factory.setLocation(location);
return factory.createMultipartConfig();
}
// MultipartResolver 객체를 Bean 으로 추가
// -> 추가 후 위에서 만든 MultipartConfig 자동으로 이용함
@Bean
public MultipartResolver multipartResolver() {
// MultipartResolver : MultipartFile 을 처리해주는 해결사
// MultipartResolver는 클라이언트로부터 받은 멀티파트 요청을 처리하고,
// 이 중에서 업로드된 파일을 추출하여 MultipartFile 객체로 제공하는 역할
// MultipartResolver 인터페이스를 상속 받은 객체 생셩
StandardServletMultipartResolver multipartResolver
= new StandardServletMultipartResolver();
return multipartResolver;
}
=> MyPageController 에서 MultipartFile 사용할 때 위 각종 설정들을 사용함
MultipartFile 이 제공하는 메서드
MyPageController
@PostMapping("file/test1")
public String fileUpload1(
@RequestParam("uploadFile") MultipartFile uploadFile,
RedirectAttributes ra
) throws Exception {
String path = service.fileUpload1(uploadFile);
// 파일이 저장되어 웹에서 접근할 수 있는 경로가 반환 되었을 때 (null 이 아닐 때)
if(path != null) {
ra.addFlashAttribute("path", path);
}
return "redirect:/myPage/fileTest";
}
MyPageServiceImpl
@Override
public String fileUpload1(MultipartFile uploadFile) throws Exception {
// 업로드한 파일이 없을 경우
if(uploadFile.isEmpty()) {
return null;
}
// 업로드한 파일이 있을 경우
// C:/uploadFiles/test/파일명 으로 서버에 저장 (이것도 마찬가지로 폴더가 존재해야함)
uploadFile.transferTo(
new File("C:\\uploadFiles\\test\\" + uploadFile.getOriginalFilename()));
// 웹에서 해당 파일에 접근할 수 있는 경로를 반환
// 서버 : C:\\uploadFiles\\test\\a.jpg
// 웹 접근 주소 : /myPage/file/a.jpg
return "/myPage/file/" + uploadFile.getOriginalFilename();
}
제출하기 하면 업로드됨 C:/uploadFiles/test/a.jpg
클라이언트가 /myPage/file/a.jpg 로 요청을 하면
path == /myPage/file/a.jpg
클라이언트가 path 를 요청하면 서버는 C:/uploadFiles/test/a.jpg
이걸 얻어다 준다는 설정이 따로 필요함
FileConfig 에서 설정해주면 됨
Spring MVC 프레임워크에서 제공하는 인터페이스 중 하나로, 스프링 구성을 커스터마이징하고 확장하기 위한 메서드를 제공함.
주로 웹 어플리케이션의 설정을 조정하거나 추가하는데 사용됨.
public class FileConfig implements WebMvcConfigurer {
인터페이스 상속
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myPage/file/**") // 클라이언트 요청 주소 패턴
.addResourceLocations("file:///C:/uploadFiles/test/");
// 클라이언트가 /myPage/file/** 이 패턴으로 이미지를 요청할 때
// 요청을 연결해서 처리해줄 서버 폴더 경로를 연결해준 거
}
요청 주소에 따라서 서버 컴퓨터의 어떤 경로에 접근할지 설정해줄 수 있음
파일 여러 개 올릴 수 있음
html
<a th:if="${path != null}"
th:text="${path}"
th:href="${path}"
download>업로드한 파일</a>
<img th:src="${path}">
html
<form action="/myPage/file/test2"
method="post"
enctype="multipart/form-data">
<h3>파일 업로드 + DB 저장 + 조회</h3>
<input type="file" name="uploadFile">
<button class="myPage-submit">제출하기</button>
</form>
DB 에 저장할 때 누가 저장하는지 알기 위해 loginMember memberNo 필요
@PostMapping("file/test2")
public String fileUpload2(
@RequestParam("uploadFile") MultipartFile uploadFile,
@SessionAttribute("loginMember") Member loginMember,
RedirectAttributes ra) {
// 로그인한 회원의 번호 (누가 업로드 했는가)
int memberNo = loginMember.getMemberNo();
// 업로드된 파일 정보를 DB에 INSERT 후 결과 행의 개수 반환 받을 예정
int result = service.fileUpload2(uploadFile, memberNo);
@Override
public int fileUpload2(MultipartFile uploadFile, int memberNo) {
// 업로드된 파일이 있는지 없는지 먼저 따져줄 거임
// 선택된 파일이 없는 경우
if(uploadFile.isEmpty()) {
return 0; // DB 에 삽입된 게 없다
}
/* DB 에 BLOB
* DB 에 파일 저장이 가능하지만
* DB 부하를 줄이기 위해
*
* DB에는 서버에 저장할 파일 경로를 저장함
* DB 삽입/수정 성공 후 서버에 파일을 저장
* 만약에 파일 저장 실패 시
* -> 예외 발생
* -> @Transactional 을 이용해서 rollback 수행
* */
// 선택된 파일이 있을 경우
// 1. 서버에 저장할 파일 경로 만들기
// 파일이 저장될 서버 폴더 경로
String folderPath = "C:\\uploadFiles\\test\\";
// 클라이언트가 파일이 저장된 폴더에 접근할 수 있도록 해주는 주소
String webPath = "/MyPage/file/";
// 위 둘은 registry 로 연결 FileConfig
// 2. DB에 전달할 데이터를 DTO 로 묶어서 INSERT 호출하기
// webPath, memberNo, 원본파일명, 변경된 파일명
// Utility 클래스 프로그램 전체적으로 사용될 유용한 기능 모음 클래스
String fileRename = Utility.fileRename(uploadFile.getOriginalFilename());
public class Utility {
public static int seqNum = 1; // 1 ~ 99999 반복
public static String fileRename(String originalFileName) {
// 20240421150314_00004.jpg 형태로 반환해줄 거임
// SimpleDateFormat : 시간을 원하는 형태의 문자열로 간단히 변경
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
// new java.util.Date() : 현재 시간을 저장한 자바 객체
String date = sdf.format(new java.util.Date());
String number = String.format("%05d", seqNum);
// 00000 만들어 놓고 seqNum 에 따라 숫자 채워줌
seqNum++; // 1 증가
if(seqNum == 100000) seqNum = 1; // 1 ~ 99999 반복
// 확장자
// "문자열".substring(인덱스)
// - 문자열을 인덱스부터 끝까지 잘라낸 결과를 반환
// "문자열".lastIndexOf(".")
// - 문자열에서 마지막 "."의 인덱스를 반환
String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
// .jpg 반환
return date + "_" + number + ext;
}
}
빌더 패턴을 이용해 객체 생성 및 초기화를 쉽게 진행
-> 기본 생성자가 자동으로 생성 안됨 따로 꼭 만들어줘야함
-> MyBatis 조회 결과를 담을 때 기본생성자로 객체를 만들기 때문에
-> 사용은 객체 생성할 때 사용
@Builder
@ToString
@NoArgsConstructor // 기본생성자
@AllArgsConstructor
@Setter
@Getter
public class UploadFile {
private int fileNo;
private String filePath;
private String fileOriginalName;
private String fileRename;
private String fileUploadDate;
private int memberNo;
private String memberNickname;
}
Builder 패턴을 이용해서 UploadFile 객체 생성
장점 1) 반복되는 참조변수명, set 구문 생략
장점 2) method chaining 을 이용해서 한줄로 작성 가능
mapper.xml 호출
MyPageServiceImpl
UploadFile uf = UploadFile.builder()
.memberNo(memberNo)
.filePath(webPath)
.fileOriginalName(uploadFile.getOriginalFilename())
.fileRename(fileRename)
.build();
int result = mapper.insertUploadFile(uf);
myPage-mapper.xml
<insert id="insertUploadFile">
INSERT INTO "UPLOAD_FILE"
VALUES(SEQ_FILE_NO.NEXTVAL, #{filePath},
#{fileOriginalName}, #{fileRename}, DEFAULT, #{memberNo})
</insert>
File 사용하면 IOException 발생
IOException 은 CheckException -> 예외 처리 필수
Service 에 @Transactional 이렇게만 작성하면
=> RuntimeException 만 잡음 -> UncheckException
=> @Transactional 이렇게만 작성하면 IOException 못 잡음
그래서 @Transactional(rollbackFor=Exception.class) 작성하는 거 (없으면 안됨 꼭 써줘야함)
// 삽입 실패 시
if(result == 0) return 0;
// 삽입 성공 시
// C:\\uploadFiles\\test\\변경된파일명 으로
// 파일을 서버 컴퓨터에 저장
uploadFile.transferTo(new File(folderPath + fileRename));
// C:\\uploadFiles\\test\\20240421150314_00004.jpg
return result;
}
String message = null;
if(result > 0) {
message = "파일 업로드 성공";
} else {
message = "파일 업로드 실패";
}
ra.addFlashAttribute("message", message);
return "redirect:/myPage/fileTest";
파일 업로드 결과