Spring Boot Board Project_14 파일 업로드TEST

송지윤·2024년 4월 21일
0

Spring Framework

목록 보기
47/65

파일 업로드 테이블 생성

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";
	}

[form태그의 enctype 속성]

enctype 이란??

  • 데이터를 서버로 전송할 때 데이터의 형태(인코딩)를 지정하는 속성

application/x-www-form-urlencoded (기본값)

  • URL 인코딩된 "문자열" 로 서버에 제출

multipart/form-data (무조건 POST에서만 동작(권장))

  • 제출되는 여러 데이터 타입에 맞춰서 인코딩하여 제출
    문자열 -> String
    숫자 -> String -> int/double(Spring이 처리)
    파일 -> 2진 데이터

GET 요청은 보안 문제, 파일명에 길이 제한 있어서 POST 권장

text/plain

  • 텍스트로 서버에 제출

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 필요

Spring 에서 파일 업로드르 처리하는 방법

  • enctype="multipart/form-data" 로 클라이언트 요청을 받으면
    (문자, 숫자, 파일 등이 섞여있는 요청)

이를 MultipartResolver를 이용해서 섞여있는 파라미터를 분리

문자열, 숫자 -> String
파일 -> MultipartFile

MultipartFile 을 이용하려면 MultipartResolver 가 어떻게 작동할지에 관한 설정 해줘야함(FileConfig)

config.properties 파일에 대한 속성 설정

파일을 디스크에 쓸 때까지의 임계값 메모리에 대한 임계값
기본값 : 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개 그냥 슬래쉬는 한개만 써도 됨) 폴더를 만들어주는 게 아니라 폴더를 직접 생성해둬야함 폴더 경로 찾아서 저장해주는 거 없으면 오류남

config.properties 내용 가져와서 FileConfig 에서 사용

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 사용할 때 위 각종 설정들을 사용함

Controller 에서 MultipartFile 이용

MultipartFile 이 제공하는 메서드

  • getSize() : 파일 크기
  • isEmpty() : 업로드한 파일이 없을 경우 true
  • getOriginalFileName() : 원본 파일 명
  • transferTo(경로) : 임시 저장 경로에서 원하는 진짜 경로로 전송하는 일
    메모리 또는 임시 저장 경로에 업로드된 파일을
    원하는 경로에 전송(서버 어떤 폴더에 저장할지 지정)

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 에서 설정해주면 됨

WebMvcConfigurer

Spring MVC 프레임워크에서 제공하는 인터페이스 중 하나로, 스프링 구성을 커스터마이징하고 확장하기 위한 메서드를 제공함.
주로 웹 어플리케이션의 설정을 조정하거나 추가하는데 사용됨.

public class FileConfig implements WebMvcConfigurer {

인터페이스 상속

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		
		registry.addResourceHandler("/myPage/file/**") // 클라이언트 요청 주소 패턴
		.addResourceLocations("file:///C:/uploadFiles/test/");
		// 클라이언트가 /myPage/file/** 이 패턴으로 이미지를 요청할 때
		// 요청을 연결해서 처리해줄 서버 폴더 경로를 연결해준 거
	}

요청 주소에 따라서 서버 컴퓨터의 어떤 경로에 접근할지 설정해줄 수 있음

input 태그 multiple 속성

파일 여러 개 올릴 수 있음

html

        <a th:if="${path != null}"
          th:text="${path}"
          th:href="${path}"
          download>업로드한 파일</a>

		<img th:src="${path}">

파일 업로드 + DB 저장 + 조회

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>

1. Controller에서 Service 호출

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);

2. 호출 받은 Service 에서 업로드된 파일이 있는지 확인 후 업로드된 파일이 있을 때 서버 저장될 파일 경로 만들기

	@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());

3. Utility 클래스 이용해서 DB에 저장될 파일 이름 변경 메서드 생성 (static 예약어 사용)

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

4. 위에서 구한 값들을 한번에 저장해서 mapper.xml 로 넘겨야해서 DTO 클래스 하나 만들어서 거기에 값 다 저장 해주기

@Builder

빌더 패턴을 이용해 객체 생성 및 초기화를 쉽게 진행
-> 기본 생성자가 자동으로 생성 안됨 따로 꼭 만들어줘야함
-> 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>

5. 가지고 온 결과 값으로 Service 에서 분기처리

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

6. Controller 결과값 분기처리

		String message = null;
		
		if(result > 0) {
			message = "파일 업로드 성공";
		} else {
			message = "파일 업로드 실패";
		}
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:/myPage/fileTest";

파일 업로드 결과

0개의 댓글