08_Spring_240417(수)_66일차(0) - ★BoardProject★ - 7. 마이페이지 - 파일 업로드

soowagger·2024년 4월 17일

8_Spring

목록 보기
19/38

7. 마이페이지 - 파일 업로드

📌 파일 업로드 세팅

config.properties 내에 파일 업로드 관련 구문 세팅

FileConfig

@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/" 경로에 맞게 물리적 폴더 생성

7-1) 업로드 테스트 1

myPage-fileTest.html

MyPage 컨트롤러

/* 파일 업로드 테스트 */
@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";
}

MyPage ServiceImpl

// 파일 업로드 테스트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();
}

7-2) 업로드 테스트 2

UploadFile DTO 생성

// @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;
}

myPage-mapper.xml

<!-- 파일 정보를 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";
}

myPage-mapper.xml

<!-- 파일 목록 조회 -->
<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>

myPage-fileList.html

7-3) 여러 파일 업로드

마이페이지 컨트롤러

/** 파일 목록 조회
 * @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;
}

profile

0개의 댓글