본문 바로가기
Spring

스프링 정리4 - 첨부파일

by moca7 2024. 11. 25.

 

 

 

 

 

- 파일 업로드(1) 세팅의 attachment 테이블.

- FILE_NO, FILE_PATH, ORIGINAL_NAME, FILESYSTEM_NAME 4개 컬럼에 추가적으로, REF_BOARD_NO 컬럼(참조 게시글 번호)이 있다.

 

 

 

 

 

- gdcampus 프로젝트의 attachment 테이블.

- FILE_NO, FILE_PATH, ORIGINAL_NAME, FILESYSTEM_NAME 4개 컬럼에 추가적으로,

EQUIP_NO, APPR_NO, UPLOAD_DATE, REF_TYPE 컬럼이 있다. 

 

 

 

 

[파일 업로드(2) 한 개의 첨부파일 업로드 - https://moca7.tistory.com/299 ]

 

- 첨부파일을 선택하는 순간 10mb를 초과하면 alert를 띄우면서 막는다.

화면단에서 유효성 검사를 할 수 있으면 하는게 좋다.

- 파일 선택이 되는 순간 script 코드가 수행되게할 수 있다. 

type이 file인 input 요소에 change라는 이벤트가 발생되는 순간(파일이 선택되었을 때, 파일 선택이 취소되었을 때)

현재 선택된 파일의 용량을 알아낼 수 있다. 10mb를 초과하면 alert를 발생시킨다.

 

- <script>의 위치도 매우 중요하다.

만약 화면상의 모든 요소가 만들어지고 <script> 태그가 만들어지는것을 원한다면 $(function(){})을 쓰면 된다.

$(function(){})을 써야 요소가 다 만들어지고 script 구문이 실행된다. 그래야 요소를 선택할 수 있다.

 

- evt.target하면 현재 이벤트가 발생한 요소를 가리킨다. 

이 input 요소에 선택된 파일들을 알아보고 싶으면 files 속성에 접근하면 된다.

- evt.target.files는 FileList 객체를 반환한다. 

파일을 하나 선택했다면 0번 인덱스에 접근해서 알아내면 되고

파일을 여러개 선택했다면 반복문으로 순차적으로 선택된 파일 요소에 접근하면 된다.

- size 속성으로 파일 용량을 알 수 있다. 파일 용량은 바이트 단위다.

- evt.target.value = "";으로 input 요소의 value값을 초기화할 수 있다.

 

- 파일을 넘길 때는 무조건 post 방식으로 넘겨야 한다.

- 파일을 넘길 때는 무조건 enctype="multipart/form-data"를 써야 파일이 넘어간다.

enctype 속성을 안쓰면 파일명이 그냥 텍스트로만 넘어간다.

 

- 다른 사용자가 이 첨부파일을 다운받을 수 있으려면 이 파일을 어딘가에 저장시켜야 한다. 

첨부파일에도 name 속성을 준다. 

 

 

 

 

- 파일은 컨트롤러의 매개변수에 MultipartFile 타입의 매개변수를 두면 된다.

이때도 매개변수명을 input 요소의 key값과 같게 하면 넘어온 파일 객체가 매개변수로 바로 전달된다.

- 각각의 매개변수에 값이 잘 담겼는지 확인용으로 로그를 출력한다.

그러려면 로그 객체가 필요하다. @Slf4j 어노테이션을 붙이면 Logger 객체를 얻어낼 필요 없이 바로 log로 쓸 수 있다. 

 

- 제목과 내용을 입력하고 파일을 선택하고 등록 버튼을 누른다.

 

 

ㅁ 값이 담겨있지 않다.

- 요청시 전달값이 담겨있지 않다. 파일뿐 아니라 제목과 내용도 주입되지 않았다.

- 스프링에서 multipart/form-data를 처리할 때 multipartResolver 설정이 누락되면 파일과 함께 전달되는 다른 폼 데이터도 제대로 처리되지 않아 DTO 객체에 값이 채워지지 않을 수 있다.

 

- 첨부파일 관련한 라이브러리가 필요하다.

라이브러리만 추가하면 되는게 아니고 또 내부적으로 사용하는 객체가 있는데,

그 객체를 빈으로 등록까지 해야 값들이 잘 담긴다.

 

 

ㅁ 스프링의 경우

- 스프링은 pom.xml에 dependency 태그로 파입 업로드 관련 라이브러리인 commons-fileupload랑 commons-io가 필요하다.

- 라이브러리 추가했다고 끝이 아니고 객체를 빈으로 등록해야 한다.

직접 만든 객체가 아니라서 어노테이션을 붙이는 방식으로는 빈으로 등록할 수 없다. 

xml이나 자바 방식으로 빈 등록해야 한다.

- root-context.xml에 파입 업로드를 위한 빈 등록을 해야 한다.

 

ㅁ 스프링 부트의 경우

- Spring Boot에서는 기본적으로 파일 업로드 처리를 지원하기 때문에 commons-fileupload와 commons-io를 pom.xml에 직접 추가하지 않아도 된다.

spring-boot-starter-web 의존성만 포함되어 있으면 파일 업로드 기능을 사용할 수 있다.

- 또 Spring Boot에서는 root-context.xml에 파일 업로드를 위한 빈 등록을 직접 추가할 필요가 없다.

Spring Boot는 자동 설정(Auto Configuration)을 통해 파일 업로드 관련 설정과 필요한 빈을 자동으로 등록한다.

 

- application.properties에 아래의 구문으로 최대 용량 제한값을 적기만 하면 된다.

# 파일 업로드 기본 설정

spring.servlet.multipart.max-file-size=10MB

spring.servlet.multipart.max-request-size=100MB

 

 

ㅁ 첨부파일을 선택하지 않고 submit

- 넘어갈 첨부파일 객체가 없어도 MultipartFile 객체가 null이 아니다. 객체가 생성은 되었다.

대신 비어있다. 비어있는 MultipartFile 객체가 넘어가게 된다.

 

 

 

 

=====================================================================

 

 

ㅁ 컨트롤러에 파일 업로드하는 구문 작성

 

 

 

- 현재 운영환경은 윈도우다. 윈도우에서 루트 디렉토리는  c드라이브다.

c드라이브 안에 upload 폴더가 만들어지고, 그 안에 날짜별로 폴더가 만들어지고 거기에 파일이 저장되게 한다. 

- 지금은 윈도우 환경이라 c드라이브에 저장되지만 개발이 끝난 후 실제 서버에 배포하면 리눅스 환경이다.

그때는 리눅스의 루트 디렉토리에 파일이 저장될 것이다.

 

- /upload/yyyyMMdd처럼 /로 시작하는 경로는 절대 경로다.

Windows에서 이는 시스템의 루트 디렉터리, 즉 C 드라이브의 루트로 간주된다.

이 경로는 C:\upload\yyyyMMdd이다.

- /는 나중에 실제 서버에 배포하면 그 환경에서의 루트 디렉터리를 가리킨다. 

 

- java.util.Date를 import 한다. 

- java.io.File를 import 한다.

 

- 파일명은 겹치면 안된다. UUID로 랜덤값을 발생시킬 수 있다.

UUID.randomUUID().toString()를 호출하면 하이픈(대쉬, -)이 4개 섞인 32자리 문자를 반환한다.

 

 

 

 

- 스프링에서는 File 객체를 생성할 때 이렇게 생성했다. File filePathDir = new File(filePath);

- 스프링은 /만 해도 c를 찾았다. 스프링은 외부 톰캣을 사용했다

- 부트는 내장 톰캣을 쓰기때문에 /가 c를 가리키지 않고 내부 경로를 뜻한다.

- 스프링 부트에서는 File 객체를 생성할 때 이렇게 생성해야 한다. File filePathDir = new File("C:" + filePath);

- 참고로 지금은 개발 할 때 테스트를 위한거다. 배포할 땐 C가 아니다.

 

- 일단 내가 원하는 경로로 파일이 저장되는지를 테스트한다.

 

- MultipartFile는 단일 파일을 처리할 때 사용하는 타입이다.

다중 파일을 처리하려면 MultipartFile[] 또는 List<MultipartFile>과 같은 배열이나 리스트 형태로 받을 수 있다.

 

 

 

 

ㅁ 스프링 부트의 경우

- 스프링은 /만 해도 c를 찾았다. 스프링은 외부 톰캣을 사용했다

- 부트는 내장 톰캣을 쓰기때문에 /가 c를 가리키지 않고 내부 경로를 뜻한다.

 

- new File("C:" + filePath);로 바꿨다.

- 참고로 지금은 개발 할 때 테스트를 위한거다. 배포할 땐 C가 아니다.

 

 

ㅁ util 패키지에 FileUtil 클래스를 따로 만들었다.

- public Map<String,String> fileupload(MultipartFile uploadFile, String folderName) { }를 만들었다.

- 이 메소드 안에 파일 업로드 구문을 작성하고 다른데서 호출해서 쓴다.

- folderName으로 서비스 별로 첨부파일이 저장될 폴더를 구분했다. 

 

 

 


=====================================================================

 

 

[ [Spring] 파일 업로드(3) 한 개의 첨부파일 업로드(2) ]

 

 

ㅁ 화면에서 업로드한 첨부 파일이 내가 지정한 경로에 내가 지정한 이름으로 저장은 되었다.

- 이제 db에 insert한다.

 

 

ㅁ 컨트롤러

- import com.br.file.dto.AttachDto; 선언.

- import com.br.gdcampus.dto.AttachDto; 선언.

- AttachDto attach = null; 선언.

 

 

- // (4) db에 기록할 정보를 자바 객체(AttachDto)에 세팅

attach = AttachDto.builder().filePath(filePath).originalName(originalFilename).filesystemName(filesystemName).build();

 

- int result = boardService.insertOneFileBoard(board, attach);

 

 

 

ㅁ 하나의 기능에 2개의 sql문을 실행해야 한다.

- 하나의 기능이기 때문에 service는 하나다. 실행할 sql문은 2개니까 2개의 dao를 호출해야 한다.

- 부모 테이블에 먼저 insert한다.

- attachment의 ref_board_no 컬럼은 board의 board_no를 참조하고 있다. (외래키 제약조건)

 

 

 

 


=====================================================================

 

 

[ [Spring] 파일 업로드(5) 다중 파일 업로드 ]

 

 

 

ㅁ 다중 파일 업로드 

- 첨부파일 업로드하는 요소는 하나만 두고 여기서 다중 파일이 선택될 수 있도록 한다. 

jsp/servlet 때는 여러개를 만들었었다.

- multiple 속성을 추가하면 파일을 선택하는 창에서 여러개를 선택해서 넣을 수 있다.

js/servlet때는 cos 라이브러리였는데 이건 다중 선택이 안되는 라이브러리였다.

- 이번엔 uploadFile이라는 key값으로 여러개의 파일이 넘어갈 수 있다.

 

 

ㅁ 컨트롤러

- 첨부파일이 하나일 때는 매개변수를 MultipartFile

첨부파일이 여러개일 때는 매개변수를 List<MultipartFile>

 

- 조건문을 if(result > 0)에서

if ( (list.isEmpty() && result == 1) || (!list.isEmpty() && result == list.size()) )으로 변경.

 

 

ㅁ 서비스(ServiceImpl)

- 컨트롤러의 조건문에서 성공 판별을 위해 result를 0으로 초기화하고, 다중 첨부파일의 경우 그 갯수만큼 누적합산시킨다.

 

 

 



=====================================================================

 

 

[ [Spring] 파일 업로드(6) 비동기식으로 첨부파일 업로드 ]

 

 

ㅁ 애초에 공통모듈로 작성하는 FileUtil 클래스에서 AttachDto 객체 자체를 담아도 되는거 아님?

굳이 map에 담고 또 map에서 꺼내야 함?

- 사실 그래도 됨. 그렇게 해도됨. 반환형만 AttachDto로 바꾸고.

- 그런데 모듈화할때는 범용적으로 쓸 수 있는 코드로 작성해주는게 제일 좋다.

~하면 무조건 AttachDto에만 담겨서 반환된다.

그런데 데이터를 담는 객체가 BoardAttachDto가 될 수도 있고 NoticeAttachDto가 될 수도 있다.

- Map

- 모듈화 작업 코드에 있어서는 ~Dto를 갖다 박는건 좋지 않다.

 

- 지금은 AttachDto에 담지만 다른거에 담길 수도 있기 때문에 Map으로 반환했다.

 

 

ㅁ 트랜잭션 관련

 

- 코드만 잘 써두면 사용자가 뭔가 잘못해서 발생하는 exception들이 대부분 runtime exception이다.

 


=====================================================================

 

 

[ [Spring] 파일 업로드(7) 첨부파일 목록 조회 ]

 

 

 

ㅁ success 함수

- resData에서 originalName을 뽑아서 a 태그 요소로 만든다. 반복문 사용.

- 그리고 href에 ${contextPath}와 파일 경로, 슬래시, DB저장파일명을 준다.

 

 

 

for(let i=0; i<resData.length; i++ {

    a += '<a href="${contextPath}' + resData[i].filePath + '/' + resData[i].filesystemName + '">' + resData[i].originalName + '</a><br>'

}

 

- 기본적으로 a 태그 클릭하면 브라우저에서 이미지, pdf가 열리게 되어 있다. 

다운로드 시키려면 다른 설정을 해야 한다.

 

 

 

for(let i=0; i<resData.length; i++ {

    a += '<a href="${contextPath}' + resData[i].filePath + '/' + resData[i].filesystemName + '" download="' + resData[i].originalName + '">' + resData[i].originalName + '</a><br>'

}

 

- download 속성을 주는것 만으로 다운 자체는 잘 된다. 그런데 파일명이 UUID로 다운된다. 

- download 속성의 값으로 원본파일명을 주면 작성하면 a 태그 클릭시 원본파일명으로 다운로드 된다. 

 

 

 


=====================================================================

 

 

[ [Spring] 파일 업로드(9) 에디터로 파일업로드 ]

 

 

- ajax로 파일을 넘길때는 항상 post 방식이고, processData과 contentType 속성을 반드시 추가해야 한다.

 

- 파일이 10개면 ajax 요청이 10번이다.

그런데 ajax는 비동기 방식이다보니까 응답이 언제 돌아올지 모른다. 실행되는 순서가 뒤죽박죽이다.

그래서 async: false를 줘서 순차적으로 실행시킬 수 있다. 

연속적으로 실행되는 ajax를 순차적으로 실행시킨다.

 

 

 


=====================================================================

 

 

[ [웹프로젝트] 20. 게시글 수정 ]

 

 

 

ㅁ 기존의 게시글 수정보다 어렵다.

- 한 게시글에 첨부파일이 여러개 있다. 다중파일 관련 수정이라 수정하기 기능의 로직이 전보다 어렵다.

 

 

 

'Spring' 카테고리의 다른 글

스프링 정리3 - AJAX  (1) 2024.11.15
스프링 정리2 - 로깅  (1) 2024.11.14
스프링 정리 - MVC  (0) 2024.11.13
디비  (1) 2024.11.06
[스프링부트] 10. 트랜잭션 처리  (0) 2024.11.06