본문 바로가기

Spring 2.5

[Spring] 파일 업로드/다운로드

스프링이 제공하는 파일 업로드 처리 기능을 사용하려면 MultipartResolver를 빈으로 등록해야 한다. 
스프링은 Apache Commons FileUpload API를 이용하여 파일 업로드를 처리하는 CommonsMultipartResolver 클래스를 제공한다
* 파일 다운로드는 어떤 프레임워크건 라이브러리가 없다. 직접 코딩해서 모듈화 해놔야함

실제주소 - /spring/fileTest/

가상주소 - /spring/file/

web.xml에 MultipartResolver 빈으로 등록

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="defaultEncoding" value="UTF-8"/>
	<property name="maxUploadSize" value="10000000"/>
</bean>

1. DB 테이블 생성

create table fileTest (
num number(7) not NULL,
subject varchar2(50) not NULL,
saveFileName varchar2(50),
originalFileName varchar2(50)); 

 

2-1. iBatis 셋팅- sqlMapConfig.xml에 resource 등록

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig      
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
	<settings 
		cacheModelsEnabled="false"
		useStatementNamespaces="true"/>

	<sqlMap resource="com/util/sqlMap/temp_sqlMap.xml"/>	
	<sqlMap resource="com/util/sqlMap/fileTest_sqlMap.xml"/>	
	<sqlMap resource="com/util/sqlMap/bbs_sqlMap.xml"/>	
</sqlMapConfig>

2-2. fileTest_sqlMap.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">


<sqlMap namespace="fileTest">

<!-- maxNum 뽑아오기 -->
<select id="maxNum" resultClass="int">
	select nvl(max(num),0) from fileTest
</select>

<!-- 전체 데이터 갯수 -->
<select id="dataCount" resultClass="int">
	select nvl(count(num),0) from fileTest
</select>

<!-- 전체 데이터 리스트 뽑아오기 -->
<select id="listData" resultClass="com.fileTest.FileCommand"
		parameterClass="map">
<![CDATA[
		select * from (select rownum rnum, data.* from ( 
		select num,subject,saveFileName,originalFileName 
		from fileTest order by num desc) data) 
		where rnum>=#start# and rnum<=#end# 	
]]>
</select>


<!-- 파일 delete 하기 -->

<!-- 1.  해당 num 정보 가져오기 -->
<select id="readData" resultClass="com.fileTest.FileCommand"
	parameterClass="int">
	select num,subject,saveFileName,originalFileName 
	from fileTest where num = #num#
</select>

<!-- 2. 삭제 하기 -->
<delete id="deleteData" parameterClass="int">
	delete fileTest where num=#num#
</delete>


<!-- 파일 올리기 (insert) -->
<insert id="insertData" parameterClass="com.fileTest.FileCommand">
	insert into fileTest(num,subject,saveFileName,originalFileName)
	values (#num#,#subject#,#saveFileName#,#originalFileName#)
</insert>

</sqlMap>

3-1. 파일업로드 페이지 - write.jsp

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
	request.setCharacterEncoding("UTF-8"); 
	String cp = request.getContextPath(); 
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>파일업로드(Spring2.5)</title>
</head>
<body>

<form action="<%=cp %>/file/created.action" method="post" enctype="multipart/form-data">

제목:<input type="text" name="subject"/><br/>
파일:<input type="file" name="upload"/><br/>

<input type="hidden" name="mode" value="save"/><br/>
<input type="submit" value="파일업로드"/>

</form>
</body>
</html>

 

뷰는 HTML의 input 태그 file 속성으로 작성하여 form 방식으로 파일 업로드 함

form 태그의 enctype 속성은 multipart/form-data로 세팅하여 브라우져가 파일 업로드 방식으로 동작하도록 설정

 

enctype 속성 값 목록

설명

multipart/form-data

파일 업로드시 사용 (인코딩 하지 않음)

application/x-www-form/urlencoded

디폴트값으로 모든 문자를 인코딩

text/plain

공백은 + 기호로 변환함. 특수문자는 인코딩하지 안함

3-2. 파일 리스트 페이지 - view.jsp

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
	request.setCharacterEncoding("UTF-8"); 
	String cp = request.getContextPath(); 
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>파일업로드(String2.5)</title>
</head>
<body>

<br/><br/>

<table width="500" align="center">
<tr height="30">
	<td align="right">
	<input type="button" value="파일 올리기"
	onclick = "javascript:location.href='<%=cp %>/file/created.action';"/>;
	</td>
</tr>	
</table>

<table width="500" align="center" border="1" style="font-size:10pt;">
<tr height="30">
	<td width="50">번호</td>
	<td width="200">제목</td>
	<td width="200">파일</td>
	<td width="50">삭제</td>
</tr>	


<c:forEach var="dto" items="${lists }">

<tr>
	<td align="center" width="50">${dto.listNum }</td>
	<td align="left" width="150">${dto.subject }</td>
	<td align="left" width="350">
	<a href="${downloadPath }?num=${dto.num}">${dto.originalFileName }</a> 
	</td>
	<td align="center" width="50">
	<a href="${deletePath }?num=${dto.num}&pageNum=${pageNum}">삭제</a></td>
</tr>

</c:forEach>

<tr>
	<td colspan="4" align="center">
		<c:if test="${totalDataCount!=0}">
			${pageIndexList }
		</c:if>
		<c:if test="${totalDataCount == 0 }">
			등록된 게시물이 없습니다
		</c:if>
	</td>	
</tr>

</table>
</body>
</html>

4. FileCommand.java (DTO역할) 생성

package com.fileTest;
import java.io.InputStream;
import org.springframework.web.multipart.MultipartFile;

public class FileCommand {
	
	private int listNum;

	private InputStream inputStream; 
	private MultipartFile upload;
	
	private int num;
	private String subject;
	private String saveFileName;
	private String originalFileName;
	
	public int getListNum() {
		return listNum;
	}
	public void setListNum(int listNum) {
		this.listNum = listNum;
	}
	public InputStream getInputStream() {
		return inputStream;
	}
	public void setInputStream(InputStream inputStream) {
		this.inputStream = inputStream;
	}
	public MultipartFile getUpload() {
		return upload;
	}
	public void setUpload(MultipartFile upload) {
		this.upload = upload;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getSaveFileName() {
		return saveFileName;
	}
	public void setSaveFileName(String saveFileName) {
		this.saveFileName = saveFileName;
	}
	public String getOriginalFileName() {
		return originalFileName;
	}
	public void setOriginalFileName(String originalFileName) {
		this.originalFileName = originalFileName;
	}
	
}	

5. FileController.java

업로드한 파일을 컨트롤러에서 MultipartFile 변수를 사용하여 전달받는다.

MultipartFile 클래스는 파일에 대한 정보와 파일 관련 메소드를 제공한다

 

대표적으로 사용하는 메소드

  • transferTo() : 파일을 저장한다
  • getOriginalFilename() : 파일 이름을 String 값으로 반환한다
  • getSize() : 파일 크기를 반환한다
  • getInputStream() : 파일에 대한 입력 스트림을 얻어온다
package com.fileTest;
import java.io.InputStream;
import org.springframework.web.multipart.MultipartFile;

public class FileCommand {
	
	private int listNum;

	private InputStream inputStream; 
	private MultipartFile upload;
	
	private int num;
	private String subject;
	private String saveFileName;
	private String originalFileName;
	
	public int getListNum() {
		return listNum;
	}
	public void setListNum(int listNum) {
		this.listNum = listNum;
	}
	public InputStream getInputStream() {
		return inputStream;
	}
	public void setInputStream(InputStream inputStream) {
		this.inputStream = inputStream;
	}
	public MultipartFile getUpload() {
		return upload;
	}
	public void setUpload(MultipartFile upload) {
		this.upload = upload;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getSaveFileName() {
		return saveFileName;
	}
	public void setSaveFileName(String saveFileName) {
		this.saveFileName = saveFileName;
	}
	public String getOriginalFileName() {
		return originalFileName;
	}
	public void setOriginalFileName(String originalFileName) {
		this.originalFileName = originalFileName;
	}
	
}

6. 파일 업로드, 다운로드, 삭제 메소드 FileManager.java 

- Struts1 업로드 방식 - FormFile 이용

- Struts2 - 따로없음. upload용 메소드를 따로 FileManager.java안에 doFileUpload 모듈화 시켜놓음

  doFileUpload(File uploadFile, String originalFileName, String path) 

- Spring2.5 - 따로없음. 모듈화. doFileUpload(InputStream is, String originalFileName, String path)

package com.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Service;

// 일반 class에서는 어노테이션 @Service 붙여주면 됨
// @Service : 이렇게만 쓰면 FileManager(class명과 똑같이)란 이름으로 객체가 생성된거임
// @Service("fileManager") : fileManager 하고 객체 생성이 된 것임
// FileManager fileManager = new FileManager(); 

@Service("fileManager")
public class FileManager {

	public static String doFileUpload(InputStream is, 
			String originalFileName, String path) throws IOException  {

		String newFileName = null;
		// A.txt 올려줘 -> 새로운파일의이름(newFileName)을 만들어서 업로드시킴(A.txt가 아니라)
		//  -> saveFileName 에 따로 담아서 저장해두겠다 
		// 즉, db에는 A.txt가 새로운파일이름을 가지고 있다고 저장해둬야함

		if(originalFileName.equals(""))
			return null;

		//파일 확장자 A.txt에서 .txt (확장자) 만 딱 빼내서 보관해뒀다가
		// 바꾼 파일이름에다가 이 저장해둔 확장자(.txt)를 끝에 붙이려고 함
		String fileExt = originalFileName
				.substring(originalFileName.lastIndexOf("."));
		// 뒤에서부터 발견한 점의 위치 부터 끝까지 추출한것을 fileExt에 집어넣겠다

		if (fileExt == null || fileExt.equals(""))
			return null; // .이 없으면 파일명이 이상한거임 => null 리턴

		newFileName = 
				String.format("%1$tY%1$tm%1$td%1$tH%1$tM%1$tS", 
						Calendar.getInstance());
		// 1$ => 뒤의 매개변수 쓰는것을 하나로 대체
		// Calendar.getInstance() 이 객체를 나눠 쓰라는 것
		// 년/월/일/시/분/초 까지 만들어짐
		newFileName += System.nanoTime(); // 파일 이름이 중복되는걸 막으려고 nanoTime으로 줌
		newFileName += fileExt; // .txt 같은 확장자를 뒤에 붙임
		//새로운 파일 이름 완성!  

		//파일경로
		File f = new File(path);
		if (!f.exists()) 
			f.mkdirs();

		String fullFileName = path + File.separator + newFileName;		
		//struts1처럼 formFile이 알아서 해주는게 아니기 때문에 직접 써야함		
		FileOutputStream fos = new FileOutputStream(fullFileName); // 내보내니깐

		int data = 0;
		byte [] buffer = new byte[1024];

		while((data=is.read(buffer, 0, 1024))!=-1) {

			fos.write(buffer, 0, data);

		}

		is.close();
		fos.close();

		return newFileName;	
		//새롭게 만든 fileName을 반환해줘야만 db에 같이 저장할 수 있음

	}


	// 파일 다운로드 필수 요건 (매개변수) 
	// 1. 서버에서 내 컴퓨터로 '다운로드' 받는거기 때문에 response가 있어야함.
	// 2. originalFileName serverFileName 을 매핑시키는 작업을 해야하므로 두개 다 필요
	// 3. 다운로드 받을 경로  	
	public static boolean doFileDownload(
			HttpServletResponse response,String saveFileName,
			String originalFileName, String path) {

		try {
			String fullPath = path + File.separator + saveFileName;

			if(originalFileName==null||originalFileName.equals("")) {

				originalFileName = saveFileName;
			}

			// 한글이름 파일을 다운받을때 깨짐 방지
			originalFileName = new String(
					originalFileName.getBytes("euc-kr"),"ISO-8859-1"); // 이렇게 써줘도 됨 8859_1

			File f = new File(fullPath);	

			if(!f.exists())
				return false;

			// 파일을받는건 response로 처리해주면 됨
			response.setContentType("application/octet-stream");  // 공식 
			response.setHeader("Content-disposition", 
					"attachment;fileName="+originalFileName); 
			// 받은 파일의 header에 딸려있는 파일의 이름을 지정해준 것


			//파일은 서버에 저장돼있음
			//서버에있는 파일을 내보내려면 일단 "먼저 파일을 읽어내서" 내보내야함
			BufferedInputStream bis = 
					new BufferedInputStream(new
							FileInputStream(f));

			// 읽어들인 파일을 "출력"한다
			OutputStream out = response.getOutputStream();

			int data;
			byte[] bytes = new byte[4096]; //4096 byte만큼 읽어서 클라이언트한테 내보낼것임

			while((data=bis.read(bytes,0,4096))!=-1) {//0~4096까지 읽어서 data에 넣을것임

				out.write(bytes,0,data); //0~data의size까지 내보낼것임 

			}

			out.flush();
			out.close();
			bis.close(); // 사용한 메소드는 다 닫아주기


		} catch (Exception e) {
			System.out.println(e.toString());
			return false; //에러가 나면 false값 리턴
		}

		return true; //정상 실행 시 
	}

	// <파일 삭제>
	//어떤 파일을 지울건지 매개변수로 경로,파일이름 받아야함
	//static으로 만들어놔서 메모리 상에 올라가있기때문에
	// 다른 곳에서 이 메소드 쓸 때 객체 생성할필요없이
	// 바로 FileManager.doFileDelete(); 써주면 됨 
	public static void doFileDelete(String fileName, String path) {

		//파일을 관리하는거니깐 try catch로 묶어줌 
		try {

			String fullPath =  path + File.separator + fileName;
			
			File f = new File(fullPath); 

			if(f.exists()) {

				f.delete(); //경로까지 찾아가서 파일이 있으면 삭제해라

			}

		} catch (Exception e) {
			System.out.println(e.toString());
		}

	}

}

7. 컨트롤러 - FileController.java

package com.fileTest;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import com.util.FileManager;
import com.util.MyUtil;
import com.util.dao.CommonDAO;

//1.controller로 등록시키기
@Controller("fileTest.fileController")
public class FileController {

	@Resource(name="dao")
	private CommonDAO dao;

	@Resource(name="myUtil")
	private MyUtil myUtil;

	@RequestMapping(value="/file/created.action", 
			method= {RequestMethod.POST, RequestMethod.GET})
	public String created(String mode, HttpSession session,
			FileCommand command,HttpServletRequest request) throws Exception{

		// 창 띄워주기
		if (command.getUpload()==null) {			
			request.setAttribute("mode", "insert");
			return "fileTest/write";
		}

		MultipartFile file = command.getUpload();
		InputStream is = file.getInputStream();

		// 1. 파일 업로드 
		String root = session.getServletContext().getRealPath("/");
		System.out.println(root);
		String savePath = root + File.separator + 
				"pds" + File.separator + "data";
		//  /pds/data/ 경로에 파일 저장할것임
		
		String originalFileName = file.getOriginalFilename();
		String saveFileName =  
				FileManager.doFileUpload(is,originalFileName, savePath);

		// 2. DB에 파일정보 저장
		if(saveFileName!=null) {

			int maxNum = dao.getIntValue("fileTest.maxNum");
			command.setNum(maxNum+1);
			command.setSaveFileName(saveFileName);
			command.setOriginalFileName(originalFileName); 
			// Spring에 의해 input값인 subject는 이미 command에 들어가 있는 상태

			dao.insertData("fileTest.insertData", command);			
		}				
		//insert 해줬으니깐 redirect
		return "redirect:/file/view.action"; 
	}

	// list는 무조건 GET방식으로만 옴
	@RequestMapping(value="/file/view.action",
			method= {RequestMethod.POST, RequestMethod.GET})
	public String view(HttpServletRequest request) throws Exception {

		HttpSession session = request.getSession();
		String cp = request.getContextPath();
		//request에 의해서 만들어 내는 데이터는 spring이 갖다주는거 아님.

		// 1. 넘어오는 pageNum
		String pageNum = request.getParameter("pageNum"); 

		String downloadPath = cp + "/file/download.action";
		String deletePath = cp + "/file/deleted.action";
		String imagePath = cp + "/pds/data";

		int currentPage = 1;

		// 2. delete할 때 세션으로 넘어오는 pageNum
		if(pageNum==null || pageNum.equals("")) 
			pageNum = (String)session.getAttribute("pageNum");

		session.removeAttribute("pageNum");

		if(pageNum!=null) // 처음 실행된게 아니면
			currentPage = Integer.parseInt(pageNum);

		//전체 데이터 구하기
		int totalDataCount = dao.getIntValue("fileTest.dataCount");

		//총 페이지 수 구하기
		int numPerPage = 3;
		int totalPage = myUtil.getPageCount(numPerPage, totalDataCount);

		if (currentPage>totalPage)
			currentPage = totalPage;

		//db에서 가져올 데이터의 시작과 끝
		int start = (currentPage-1)*numPerPage+1;
		int end = currentPage*numPerPage;

		// 전체 파일 list 뽑아오기
		Map<String,Object> hMap = new HashMap<String,Object>();
		hMap.put("start", start);
		hMap.put("end", end);

		List<Object> lists = 
				dao.getListData("fileTest.listData", hMap);


		// 고정 일렬번호를 만들기
		Iterator<Object> it = lists.iterator();
		int listNum,n=0;

		while(it.hasNext()) {

			FileCommand dto = (FileCommand)it.next();
			listNum = totalDataCount - (start + n-1 );
			dto.setListNum(listNum);
			n++;

		}

		// ◀이전 6 7 8 9 10 다음▶ 표시 링크 
		String urlList = cp + "/file/view.action";
		
		request.setAttribute("lists", lists);
		request.setAttribute("pageIndexList", 
				myUtil.pageIndexList(currentPage, totalPage, urlList));
		request.setAttribute("pageNum", pageNum);
		request.setAttribute("totalDataCount", totalDataCount);
		request.setAttribute("downloadPath", downloadPath);
		request.setAttribute("deletePath", deletePath);
		request.setAttribute("imagePath", imagePath);
		
		return "fileTest/view"; 		
	}
	
	@RequestMapping(value="/file/download.action",
			method= {RequestMethod.GET})
	public String download(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		
		int num = Integer.parseInt(request.getParameter("num")); 
		
		// 파일 저장할 위치정보 불러오기
		HttpSession session = request.getSession();
		
		String root = session.getServletContext().getRealPath("/");
		String savePath = root + File.separator + 
				"pds" + File.separator + "data";
		
		// DB에서 파일 정보 불러오기
		FileCommand command = 
				(FileCommand)dao.getReadData("fileTest.readData", num);
		
		FileManager.doFileDownload(response,command.getSaveFileName(), 
				command.getOriginalFileName(), savePath);
		
		return null;
		// 다운로드는 반환값이 null ! 		
	}	

	@RequestMapping(value="/file/deleted.action",
			method= {RequestMethod.GET})
	public String deleted(HttpServletRequest request) 
			throws Exception {

		String pageNum = request.getParameter("pageNum");
		int num = Integer.parseInt(request.getParameter("num"));

		dao.deleteData("fileTest.deleteData", num);

		return "redirect:/file/view.action?pageNum=" + pageNum; 
		// 여긴 pageNum이 바로 왔으므로, 세션에 올릴필요 없이 GET방식으로 줘도 됨

	}

}

'Spring 2.5' 카테고리의 다른 글

JDK 버전 바꾸기  (0) 2019.12.16
SPRING 2.5 - 컨트롤러 종류별 예제, annotation  (0) 2019.10.28