config.properties 내에 파일 업로드 관련 구문 세팅
@Configuration
@PropertySource("classpath:/config.properties")
public class FileConfig implements WebMvcConfigurer {
// WebMvcConfigurer : Spring MVC 프레임워크에서 제공하는 인터페이스 중 하나로,
// 스프링 구성을 커스터마이징하고, 확장하기 위한 메서드를 제공함
// 주로 웹 어플리케이션의 설정을 조정하거나 추가하는데 사용됨
// 파일 업로드 임계값
@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;
// 요청 주소에 따라 서버 컴퓨터의 어떤 경로에 접근할지 설정
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myPage/file/**") // 클라이언트 요청 주소 패턴
.addResourceLocations("file:///C:/uploadFiles/test/");
// 클라이언트가 /myPage/file/** 패턴으로 이미지를 요청할 때
// 요청을 연결해서 처리해줄 서버 폴더 경로 연결
}
/* MultipartResolver 설정 */
@Bean
public MultipartConfigElement configElement() {
// MultipartConfigElement :
// - 파일 업로드를 처리하는데 사용되는 MultipartConfigElement를 구성하고 반환
// - 파일 업로드를 위한 구성 옵션을 설정하는데 사용
// - 업로드 파일의 최대 크기, 메모리에서의 임시 저장 경로 등을 설정 가능
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을 처리해주는 해결사..
// - 클라이언트로부터 받은 멀티파트 요청을 처리하고
// 이 중에서 업로드된 파일을 추출하여 MultipartFile 객체로 제공하는 역할
StandardServletMultipartResolver multipartResolver
= new StandardServletMultipartResolver();
return multipartResolver;
}
}
"file:///C:/uploadFiles/test/" 경로에 맞게 물리적 폴더 생성

/* 파일 업로드 테스트 */
@GetMapping("fileTest")
public String fileTest() {
return "myPage/myPage-fileTest";
}
/*
* Spring에서 파일 업로드를 처리하는 방법
*
* - enctype="multipart/form-data"로 클라이언트 요청을 받으면
* (문자, 숫자, 파일 등이 섞여있는 요청)
*
* 이를 MultipartResolver(FileConfig에 정의)를 이용해서
* 섞여 있는 파라미터를 분리
*
* 문자열, 숫자 -> String
* 파일 -> MultipartFile
*/
/** 파일 업로드 테스트1
* @param uploadFile : 업로드한 파일 + 파일에 대한 내용 및 설정 내용
* @return
*/
@PostMapping("file/test1")
public String fileUpload1(
/*@RequestParam("memberName") String memberName,*/
@RequestParam("uploadFile") MultipartFile uploadFile,
RedirectAttributes ra) throws Exception {
String path = service.fileUpload1(uploadFile);
// 파일이 저장되어 웹에서 접근할 수 있는 경로가 반환되었을 때
if(path != null) {
ra.addFlashAttribute("path", path);
}
return "redirect:/myPage/fileTest";
}
// 파일 업로드 테스트1
@Override
public String fileUpload1(MultipartFile uploadFile) throws Exception {
// MultipartFile이 제공하는 메서드
// - getSize() : 파일 크기
// - isEmpty() : 업로드한 파일이 없을 경우 true
// - getOriginalFileName() : 원본 파일명
// - transferTo(경로) :
// 메모리 또는 임시 저장 경로에 업로드된 파일을
// 원하는 경로에 전송(서버의 어떤 폴더에 저장할지 지정)
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();
}

// @Builder : 빌더 패턴을 이용해 객체 생성 및 초기화를 쉽게 진행
// -> 기본 생성자가 생성 안됨
// -> MyBatis 조회 결과를 담을 때 기본 생성자로 객체를 만들기 때문
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UploadFile {
private int fileNo;
private String filePath;
private String fileOriginalName;
private String fileRename;
private String fileUploadDate;
private int memberNo;
private String memberNickname;
}
/** 파일 업로드 테스트2
* @param uploadFile
* @param loginMember
* @param ra
* @return
* @throws IOException
*/
@PostMapping("file/test2")
public String fileUpload2(
@RequestParam("uploadFile") MultipartFile uploadFile,
@SessionAttribute("loginMember") Member loginMember,
RedirectAttributes ra) throws IOException {
// 로그인한 회원의 번호 (누가 업로드 했는가)
int memberNo = loginMember.getMemberNo();
// 업로드된 파일 정보를 DB에 INSERT 후 결과 행의 개수 반환 받을 예정
int result = service.fileUpload2(uploadFile, memberNo);
String message = null;
if(result > 0) {
message = "파일 업로드 성공!";
} else {
message = "파일 업로드 실패..";
}
ra.addFlashAttribute("message", message);
return "redirect:/myPage/fileTest";
}
// 파일 업로드 테스트2 (+DB)
@Override
public int fileUpload2(MultipartFile uploadFile, int memberNo) throws IOException {
// 업로드된 파일이 없다면
// == 선택된 파일이 없을 경우
if(uploadFile.isEmpty()) {
return 0;
}
/* DB에 파일 저장이 가능은 하지만
* DB 부하를 줄이기 위해서
*
* 1) DB에는 서버에 저장할 파일 경로를 저장
*
* 2) DB 삽입/수정 성공 후 서버에 파일을 저장
*
* 3) 만약에 파일 저장 실패 시
* -> 예외 발생
* -> @Transactional을 이용해서 rollback 수행
*/
// 1. 서버에 저장할 파일 경로 만들기
// 파일이 저장될 서버 폴더 경로
String folderPath = "C:\\uploadFiles\\test\\";
// 클라이언트가 파일이 저장된 폴더에 접근할 수 있는 주소
String webPath = "/myPage/file/";
// 2. DB에 전달할 데이터를 DTO로 묶어서 INSERT 호출하기
// webPath, memberNo, 원본 파일명, 변경된 파일명
String fileRename = Utility.fileRename(uploadFile.getOriginalFilename());
/* 기존 객체 생성 방법
UploadFile uf = new UploadFile();
uf.setMemberNo(memberNo);
uf.setFilePath(webPath);
uf.setFileOriginalName(uploadFile.getOriginalFilename());
uf.setFileRename(fileRename);
*/
// Builder 패턴을 이용해서 UploadFile 객체 생성
// 장점 1) 반복되는 참조변수명, set 구문 생략
// 장점 2) method chaining을 이용해서 한 줄로 작성 가능
UploadFile uf = UploadFile.builder()
.memberNo(memberNo)
.filePath(webPath)
.fileOriginalName(uploadFile.getOriginalFilename())
.fileRename(fileRename)
.build();
int result = mapper.insertUploadFile(uf);
// 3. 삽입(INSERT) 성공 시 파일을 지정된 서버 폴더에 저장
// 삽입 실패 시
if(result == 0) return 0;
// 삽입 성공 시
// C:\\uploadFiles\\test\\변경된 파일명 으로
// 파일을 서버 컴퓨터에 저장
uploadFile.transferTo(new File(folderPath + fileRename));
// C:\\uploadFiles\\test\\20240417111705_00001.jpg
// -> CheckedException 발생 -> 예외 처리 필수
// @Transactional은 RuntimeException(UncheckedException 대표)만 처리
// -> rollbackFor 속성 이용해서 롤백할 예외 범위를 수정
// --> @Transactional(rollbackFor=Exception.class)
return result;
}
<!-- 파일 정보를 DB에 삽입-->
<insert id="insertUploadFile">
INSERT INTO "UPLOAD_FILE"
VALUES(SEQ_FILE_NO.NEXTVAL, #{filePath}, #{fileOriginalName}, #{fileRename}, DEFAULT, #{memberNo})
</insert>



/** 파일 목록 조회
* @param model
* @return
*/
@GetMapping("fileList")
public String fileList(Model model) {
// 파일 목록 조회 서비스 호출
List<UploadFile> list = service.fileList();
// model list 담아서
model.addAttribute("list", list);
// myPage/myPage-fileList.html
return "myPage/myPage-fileList";
}
<!-- 파일 목록 조회 -->
<select id="fileList">
SELECT FILE_NO, FILE_PATH, FILE_ORIGINAL_NAME, FILE_RENAME, MEMBER_NICKNAME,
TO_CHAR(FILE_UPLOAD_DATE, 'YYYY-MM-DD') FILE_UPLOAD_DATE
FROM "UPLOAD_FILE"
JOIN "MEMBER" USING(MEMBER_NO)
ORDER BY FILE_NO DESC
</select>


/** 파일 목록 조회
* @param model
* @return
*/
@GetMapping("fileList")
public String fileList(Model model) {
// 파일 목록 조회 서비스 호출
List<UploadFile> list = service.fileList();
// model list 담아서
model.addAttribute("list", list);
// myPage/myPage-fileList.html
return "myPage/myPage-fileList";
}
@PostMapping("file/test3")
public String fileUpload3(
@RequestParam("aaa") List<MultipartFile> aaaList,
@RequestParam("bbb") List<MultipartFile> bbbList,
@SessionAttribute("loginMember") Member loginMember,
RedirectAttributes ra) throws Exception{
// aaa 파일 미제출 시
// -> 0번, 1번 인덱스 파일이 모두 비어있음
// bbb(multiple) 파일 미제출 시
// -> 0번 인덱스 파일이 비어있음
int memberNo = loginMember.getMemberNo();
// result == 업로드 파일 개수
int result = service.fileUpload3(aaaList, bbbList, memberNo);
String message = null;
if(result == 0) {
message = "업로드된 파일이 없습니다.";
} else {
message = result + "개의 파일이 업로드 되었습니다!";
}
ra.addFlashAttribute("message", message);
return "redirect:/myPage/fileTest";
}
// 여러 파일 업로드
@Override
public int fileUpload3(List<MultipartFile> aaaList, List<MultipartFile> bbbList, int memberNo) throws Exception {
// 1. aaaList 처리
int result1 = 0;
// 업로드된 파일이 없을 경우를 제외하고 업로드
for(MultipartFile file : aaaList) {
if(file.isEmpty()) { // 파일이 없으면 다음 파일
continue;
}
// fileUpload2 메서드 호출(재활용)
// -> 파일 하나 업로드 + DB INSERT
result1 += fileUpload2(file, memberNo);
}
// 2. bbbList 처리
int result2 = 0;
// 업로드된 파일이 없을 경우를 제외하고 업로드
for(MultipartFile file : bbbList) {
if(file.isEmpty()) { // 파일이 없으면 다음 파일
continue;
}
// fileUpload2 메서드 호출(재활용)
// -> 파일 하나 업로드 + DB INSERT
result2 += fileUpload2(file, memberNo);
}
return result1 + result2;
}

