How to upload Files to Server from Client.
안녕하세요. 이번 수업때 Spring Framework 에서 파일을 업로드 하는 방법을 배웠는데요..
생각보다 유효성 검사도 신경 써야할게 많았고 순서도 복잡하니, 많은 반복문과 조건문이 괄호를 끼고
개판을 치다 보니 순서를 정리해볼겸 글을 쓰게 되었습니다!…
파일을 업로드 하는 jsp 페이지로 이동시
가져가야할 데이터가 있을까요??
당연히 세션에 부여되 있는 userid를 가져가야겠죠??
하지만 세션은 서버단에서 저장이 되있기 때문에 어디서든지 호출이 가능합니다.
그래서 특별히 get, post로 보내줘야할 데이터는 없습니다.
1. FileUpload를 위한 세팅
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
root-context.xml 파일에서 먼저 multipartResolver로 bean 객체를 생성해줘야 합니다.
그래야지 자바에서 multipartReslover 객체를 사용할 수 있습니다.
Spring에서 제공하는 Multipart 기능을 사용하기 위해서는 위와 같은 설정을 먼저 해줘야합니다.
multipartResolve 의 역할은 ⇒ multipart 로 데이터가 전송이 된 경우에 해당 데이터를 스프링 MVC에서 사용할 수 있도록 변환해주는 역할을 하게 됩니다.
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
또한 pom.xml 에서 파일을 업로드 하기 위한 디펜더시도 설정을 해줘야 합니다.
위와 같은 코드들은 “메이븐 저장소”에 가셔서 검색하면 다 나옵니다!
2. 파일명들을 저장할 VO 생성
기존에 로그인과 관련된 회원정보 테이블을 만들때 미리 넣어놔도 상관은 없지만, 기능들을 세세하게 나눠놓을 수록
추후에 재이용성이 높아지고 필요한 부분문 골라서 데이터를 사용하고 저장하기 편하니 따로 VO클래스를 하나 생성해서
필요한 변수들을 선언하고, getter&setter 를 구현해놓는다 (Spring에서 알아서 변수명이 VO객체의 변수명과 일치하면 알아서 값을 담아준다)
3. Upload.jsp 페이지에서의 작업
제일 먼저 기본적인 레이아웃을 구성해줍니다.
그리고 난 다음에 input태그의 type=”file” 로 지정해주고 submit발생시 데이터를 가져가야하기 때문에
name=”변수명” 을 설정해줍니다.
<input type="file" name="filename" id="filename">
자 이러면 파일을 보낼 준비가 끝났을까요?
아닙니다. Form태그에서 추가로 설정해줘야하는 요소가 하나 존재합니다.
바로 어떤 파일의 확장자를 선택거고, 보낼지 설정해줘야 합니다.
<form method="post" action="/매핑주소" enctype="multipart/form-data">
여기서 사용한 요소는 ecntype입니다.
enctype 요소는 from태그의 데이터가 서버로 제출될 때 해당 데이터가 인코딩 되는 방법을 명시합니다!
(form 태그의 method=”post” 방식일때만 사용이 가능합니다)
여기서 사용한 속성값은 ⇒
여기까지 완료 되었다면, 이제는 파일이 정상적으로 전송이 완료 될겁니다.
4. Controller에서의 파일 및 파일명 저장 방법
form 태그의 action태그의 매핑된 주소에 따라 디스패처 서블릿이 해당되는 컨트롤러를 호출하게 됩니다.
여기서부터 조금 복잡해집니다 !…
일단 데이터베이스에서 결과값을 받기전에 보내줘야 하는 데이터에 가공이 필요합니다.
- server에서 파일을 저장하는 공간에 동일한 파일명이 들어온다면?
- 만약에 클라이언트가 파일을 하나도 첨부하지 않았다면?
- 파일을 첨부한 만큼 데이터를 전송해줘야 한다 (반복문) ⇒ 여러개를 담을 수 있는 배열(컬랙션)이 필요하다.
- 파일을 업로드할 저장공간의 절대주소를 구해야합니다
- 업로드가 된 파일들은 multipart 객체가 가지고 있습니다. HttpServletRequest의 객체를 통해 캐스팅을 해서 MultipartHttpServletRequest 객체에 저장해줍니다. ⇒ 업로드 된 파일 수 만큼의 multipart 객체를 생성해줍니다.
- 데이터베이스에 파일명이 저장되있어야지 다운로드를 할때 해당 경로에서 파일명을 찾아 다운을 할 수 있습니다. 그래서 데이터베이스에 파일명을 배열(컬랙션)에 담아서 보내줘야 합니다.
- 만약에 데이터베이스에 파일명 추가 실패시 이미 업로드 되있는 파일들을 삭제해줘야 합니다.
신경써야할 부분들이 꽤 있죠?……ㅠ
그래도 차근차근 해봅시다. 조건이야 많지만 하나씩 조건사항을 처리하다 보면 그렇게 어려운건 없습니다!
4-1. 필요한 객체들 생성
ModelAndView mav = new ModelAndView();
MultipartHttpServletRequest mphsr = (MultipartHttpServletRequest)request;
List<MultipartFile> files = mphsr.getFiles("filename");
List<String> fileNameList = new ArrayList<String>();
vo.setUserid((String)request.getSession().getServletContext().getRealPath("/upload"));
자 순서대로 해당 객체들이 무엇을 위해 필요하진 명시해보겠습니다!
- ModelAndView ⇒ 바로 서버에서 redirect를 해서 페이지를 보낼 수 있지만, 만약에 실패시 전 페이지에 있는 데이터는 남긴 상태로 돌아가야해서 history.back()을 자바스크립트에서 쓰기위해 jsp페이지를 만들고 이동할때 결과값을 가지고 돌아가기 위해 Model, ModelAndView 객체를 생성해줍니다.
- jsp단에서 파일업로드시 request객체에 정보들이 담기는데 이걸 파일객체로 만들기 위해서 request 객체를 MultipartHttpServletRequset객체로 형변환해서 객체에 담아준다.
- 업로드된 파일이 하나가 아니라 여러개로 설정할 수 있으니 List컬랙션을 사용하여 데이터 값들을 저장 해준다. 이때 mphsr객체의 getFiles메소드에 name=”변수명” 을 넣어주게 되면 해당 되는 데이터들이 있는 만큼 파일 객체로 저장이 된다.
- fileNameList는 데이터베이스에 전송해줄 용도로 생성된 객체로 추후에 다운로드를 클라이언트가 하게 되면, 경로와 파일명이 필요한데, 이때 파일명을 데이터베이스에 저장하고 가져오는 형식으로 동작하기에, 담아서 데이터베이스에 전송해줄 객체가 필요하다 (여러개일수도 있기때문)
- 해당 게시글을 올리는 유저는 당연히 로그인이 되있는 상태며, 데이터 베이스에서 참조키로 다른 테이블과 연동을 이미 해놨기에, session에서 아이디를 구하고 그것을 DataVO 객체에 있는 아이디에 setter를 해준다.
4-2 파일명이 업로드 폴더안에 있는 파일명과 일치할때
기존에 이미 누군가 파일을 올려놓은게, 추후에 올린 사람과 파일명이 일치할때, 서버에서는 이를 인지하고 Rename을 해줘야지
데이터가 대치가 되지않고, 계속해서 서버 폴더에 저장이 될 수 있다.
그럴려면 일단 업로드된 file들의 값이 비어있지는 않은지 먼저 검사해야 하고, 비어있지 않다면 파일이 있는 만큼 파일명을 검사하고 rename을
해주는 작업이 필요하다.
if(files!=null){
for(int i=0; i<files.size(); i++){
MultipartFile mpf = files.get(i);
String name = mpf.getOriginalFilename();
if(name!=null && !name.equals("")){
File file = new File(path,name);
if(file.exists()){
for(int num=1;;num++){
int dot = name.lastIndexOf(".");
String front = name.substring(0,dot);
String end = name.substring(dot+1);
String newName = front+"_"+num+"."+end;
file = new File(path, newName);
if(!file.exists()){
name = newName;
break;
}
}
}
try {
mpf.transferTo(new File(path, name));
}catch(IOException e) {
e.getMessage();
}
fileNameList.add(name);
}
}
vo.setFilename(fileNameList.get(0));
}
int result = service.dataInsert(vo);
if(result==0) {
for(int i=0; i<fileNameList.size(); i++) {
File delFile = new File(path, fileNameList.get(i));
delFile.delete();
}
mav.setViewName("data/dataFormResult");
}else {
mav.setViewName("redirect:dataList");
}
return mav;
}
괄호지옥에 빠진 코드……..
근데 어려운건 사실 하나도 없습니다 ㅎ
순서대로 한번 차근차근 나눠서 보겠습니다.
<aside> 💡 VO 객체는 매개변수로 생성을 해놓은 상태이고, HttpServletRequest 역시 매개변수로 생성해놨습니다.
</aside>
if(files!=null){
for(int i=0; i<files.size(); i++){
MultipartFile mpf = files.get(i);
String name = mpf.getOriginalFilename();
만약에 multipartFile 객체를 담는 List에 데이터가 있다면 !
반복분을 통해서 파일 객체를 받고, 객체를 통해서 파일명을 얻어옵니다.
if(name!=null && !name.equals("")){
File file = new File(path,name);
만약에 받아온 파일의 이름이 없는경우는 제외시키고 파일 객체를 생성하여 경로와 파일이름을 담아줍니다.
if(file.exists()){
for(int num=1;;num++){
int dot = name.lastIndexOf(".");
String front = name.substring(0,dot);
String end = name.substring(dot+1);
String newName = front+"_"+num+"."+end;
file = new File(path, newName);
if(!file.exists()){
name = newName;
break;
}
}
}
만약에 파일 객체에 내용물이 존재하다면.
반복문을 통해서 파일명, 확장자를 구별해주고 만약에 지정해놓은 업로드 폴더에 해당 파일명이 존재하다면
파일명 + _ num + 확장자를 해줍니다.
만약에 바꾼 파일명 역시 존재하다면, 무한반복으로 해놨기에 파일명이 존재하지 않을때 까지 반복을 하게 됩니다.
4-3. 파일명 검사가 끝나고 업로드 및 DB에 보내기 위한 준비
try {
mpf.transferTo(new File(path, name));
}catch(IOException e) {
e.getMessage();
}
fileNameList.add(name);
}
}
vo.setFilename(fileNameList.get(0));
}
multipartFile 객체를 통해 transferTo 메소드 ( 파일을 실제 업로드 해주는 메소드) 를 실행시켜 경로와 이름을 담아주고 업로드합니다.
예외처리를 필수로 해줘야 하기에 IOException 을 추가해줬습니다.
그리고 DB에서 작업을 해야하기에 DB에 저장할 파일명을 List에 담아줍니다.
그리고 마지막으로 VO객체에 List를 통해서 파일명을 set 해줍니다 !
5. DB 연동후 result에 따른 결과 반환.
int result = service.dataInsert(vo);
if(result==0) {
for(int i=0; i<fileNameList.size(); i++) {
File delFile = new File(path, fileNameList.get(i));
delFile.delete();
}
mav.setViewName("data/dataFormResult");
}else {
mav.setViewName("redirect:dataList");
}
return mav;
}
그리고 미리 Mapper에서 작성해놓은 쿼리문을 실행 시킨 결과 값을 result에 담아주고
해당 값이 0이라면 실패 1이라면 성공.
그래서 만약에 해당값이 0이라면, DB에는 파일에 해당되는 파일명이 들어가지 않았기에 업로드 된 파일 역시
파일 객체를 생성해 List를 통해 파일명을 받아오고 파일 객체의 메소드인 delete() 를 실행시켜 업로드 된 파일을
삭제해줍니다.
그리고 성공 결과 여부에 따라서 보내는 View가 다르기에 else문을 추가하여 0이 아니라면 성공이니
다른 뷰 페이지로 넘기고, 실패시 history를 쓰기 위한 jsp 파일로 보냅니다.
매퍼 클래스는 따로 조건을 추가 할게 없기에 (업로드 파일을 1개만 설정했기에) 보여드리지 않았습니다.
그냥 쿼리문 작성하는거랑 크게 다를게 없어요 허헣!
느낀점..
처음에 코드를 따라치면서 수업을 들을때는 어우…”복잡하네” 라고 생각이 들었지만,
역시 한번 내손으로 짠 코드는 다음에 또 보면 “뭐야 생각보다 간단하네?” 라는 생각이 드는건 “진리” 같다….
복잡한 식이 들어간 부분도 없고 단순히 유효성검사를 하는데 if, for문이 많다 보니 괄호지옥이여서 헷갈릴 수도 있을거 같다는
생각이 들었다.
끘!
'Spring & Spring Boot' 카테고리의 다른 글
Spring bean(빈이란?) (0) | 2023.01.16 |
---|---|
IOC (Inversion Of Control) 제어의 역전 .feat “Container” (0) | 2023.01.16 |
DI (Dependency Injection) 의존성 주입 (0) | 2023.01.16 |
Spring Interceptor 핸들러 가로채기 🙂 (4) | 2023.01.16 |
MVC 패턴?…What the….?? (0) | 2023.01.16 |