ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링(Spring) 개발 - (14) 파일 업로드 & 다운로드 (2/3)
    Spring 2015.07.20 10:37

    이번글에서는 첨부파일의 다운로드에 대해서 이야기를 하려고 합니다.


    지난글에서 첨부파일을 업로드하였고, 이번글에서는 그 파일을 다운로드 하는 방법입니다.


    그리고 다음글에서는 기존 소스를 약간 변경하여 다중 첨부파일 업로드를 하는 방법을 이야기하겠습니다.


    ------------------------------------------------------------------------------------


    1. 첨부파일 보여주기

    지난글에서는 게시판에 첨부파일을 등록하는 기능을 작성했었다. 이제 해당 게시글에서 첨부파일을 보여주는것을 먼저 시작하자.


    1. SQL

    이번에는 쿼리부터 시작을 해보자. 

    다음의 쿼리를 Sample_SQL.xml 파일에 작성하자.

    <select id="selectFileList" parameterType="hashmap" resultType="hashmap">
    	<![CDATA[
    		SELECT
    		    IDX,
    		    ORIGINAL_FILE_NAME,
    		    ROUND(FILE_SIZE/1024,1) AS FILE_SIZE
    		FROM
    		    TB_FILE
    		WHERE
    		    BOARD_IDX = #{IDX}
    		    AND DEL_GB = 'N'
    	]]>
    </select>
    

    쿼리는 간단하다. 선택된 게시글 번호에 해당하는 첨부파일의 목록을 조회하는 쿼리이다.

    첨부파일의 크기를 Kb 단위로 보여주기 위해서 ROUND 함수를 사용하였다. 

    그 외에는 별다른게 없는 쿼리임을 알 수 있다.


    2. Java

    이제 java단을 수정할 차례이다. 

    먼저 기존 소스를 수정할 SampleController와 SampleServiceImp을 살펴보자.


    1) SampleController

    SampleController.java 파일에서 게시글의 상세정보를 가져오는 openBoardDetail 부분을 다음과 같이 변경하자.

    @RequestMapping(value="/sample/openBoardDetail.do")
    public ModelAndView openBoardDetail(CommandMap commandMap) throws Exception{
    	ModelAndView mv = new ModelAndView("/sample/boardDetail");
    	
    	Map<String,Object> map = sampleService.selectBoardDetail(commandMap.getMap());
    	mv.addObject("map", map.get("map"));
    	mv.addObject("list", map.get("list"));
    	
    	return mv;
    }
    

    기존 소스와 비교했을 때, 큰 변화는 없다. 

    살펴봐야 할것은 6,7번째 줄이다. 

    기존에는 sampleService.selectBoardDetail()의 리턴값을 그대로 map이라는 이름으로 바로 화면으로 전송하였는데, 

    이번에는 map에서 2가지를 가져온 후, 각각 mv에 넣어주는 것을 확인해야한다.

    6번째 줄의 map.get("map")은 기존의 게시글 상세정보이다. 

    7번째 줄의 map.get("list")는 첨부파일의 목록을 가지고 있다. 게시글 상세정보와 첨부파일 정보를 각각 보내주는것을 확인하자.

    그럼 다음으로 SampleServiceImpl을 수정하자.


    2) SampleServiceImpl

    SampleServiceImp.java 파일에서 selectBoardDetail 부분을 다음과 같이 변경하자.

    @Override
    public Map<String, Object> selectBoardDetail(Map<String, Object> map) throws Exception {
    	sampleDAO.updateHitCnt(map);
    	Map<String, Object> resultMap = new HashMap<String,Object>();
    	Map<String, Object> tempMap = sampleDAO.selectBoardDetail(map);
    	resultMap.put("map", tempMap);
    	
    	List<Map<String,Object>> list = sampleDAO.selectFileList(map);
    	resultMap.put("list", list);
    	
    	return resultMap;
    }
    

    지난글과 비교해서 약간 수정이 된 것을 알 수 있다.

    먼저 6번째 줄 sampleDAO.selectBoardDetail()을 통해서 게시글의 상세정보를 가져온다. 

    그리고 그 결과값을 "map" 이라는 이름으로 resultMap에 저장한다.


    그 다음으로 sampleDAO.selectFileList()를 통해서 게시글의 첨부파일 목록을 가져온다.

    앞에서 각 게시글에는 하나의 첨부파일만 저장할 수 있도록 했었지만, 곧 다중 업로드가 가능하도록 수정할 계획이라서

    가능한 소스의 수정을 적게하도록, 미리 첨부파일의 목록을 가져오도록 하였다.

    그 다음으로 resultMap에 "list"라는 이름으로 저장하였다. 


    여기서 resultMap에 "map"과 "list"라는 키를 다시한번 확인하자.

    이 키는 앞의 SampleController에서 map.get("map), map.get("list") 라는 키로 사용되었다. 


    이제 마지막으로 selectFileList 메서드를 구현하자.


    3) SampleDAO

    SampleDAO.java에 다음의 소스를 작성하자.

    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> selectFileList(Map<String, Object> map) throws Exception{
    	return (List<Map<String, Object>>)selectList("sample.selectFileList", map);
    }
    

    맨 처음에 만들었던 selectFileList 쿼리를 호출하는 역할을 한다.


    이제 여기까지 작성했으면, 쿼리 및 서버부분은 끝났다. 

    마지막으로 JSP화면에서 첨부파일 목록을 보여주면 된다.


    3. JSP

    boardDetail.jsp를 다음과 같이 변경하자.

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html lang="ko">
    <head>
    <%@ include file="/WEB-INF/include/include-header.jspf" %>
    </head>
    <body>
    	<table class="board_view">
    		<colgroup>
    			<col width="15%"/>
    			<col width="35%"/>
    			<col width="15%"/>
    			<col width="35%"/>
    		</colgroup>
    		<caption>게시글 상세</caption>
    		<tbody>
    			<tr>
    				<th scope="row">글 번호</th>
    				<td>${map.IDX }</td>
    				<th scope="row">조회수</th>
    				<td>${map.HIT_CNT }</td>
    			</tr>
    			<tr>
    				<th scope="row">작성자</th>
    				<td>${map.CREA_ID }</td>
    				<th scope="row">작성시간</th>
    				<td>${map.CREA_DTM }</td>
    			</tr>
    			<tr>
    				<th scope="row">제목</th>
    				<td colspan="3">${map.TITLE }</td>
    			</tr>
    			<tr>
    				<td colspan="4">${map.CONTENTS }</td>
    			</tr>
    			<tr>
    				<th scope="row">첨부파일</th>
    				<td colspan="3">
    					<c:forEach var="row" items="${list }">
    						<input type="hidden" id="IDX" value="${row.IDX }">
    						<a href="#this" name="file">${row.ORIGINAL_FILE_NAME }</a> 
    						(${row.FILE_SIZE }kb)
    					</c:forEach>
    				</td>
    			</tr>
    		</tbody>
    	</table>
    	<br/>
    	
    	
    	<a href="#this" class="btn" id="list">목록으로</a>
    	<a href="#this" class="btn" id="update">수정하기</a>
    	
    	<%@ include file="/WEB-INF/include/include-body.jspf" %>
    	<script type="text/javascript">
    		$(document).ready(function(){
    			$("#list").on("click", function(e){ //목록으로 버튼
    				e.preventDefault();
    				fn_openBoardList();
    			});
    			
    			$("#update").on("click", function(e){ //수정하기 버튼
    				e.preventDefault();
    				fn_openBoardUpdate();
    			});
    			
    			$("a[name='file']").on("click", function(e){ //파일 이름
    				e.preventDefault();
    			});
    		});
    		
    		function fn_openBoardList(){
    			var comSubmit = new ComSubmit();
    			comSubmit.setUrl("<c:url value='/sample/openBoardList.do' />");
    			comSubmit.submit();
    		}
    		
    		function fn_openBoardUpdate(){
    			var idx = "${map.IDX}";
    			var comSubmit = new ComSubmit();
    			comSubmit.setUrl("<c:url value='/sample/openBoardUpdate.do' />");
    			comSubmit.addParam("IDX", idx);
    			comSubmit.submit();
    		}
    	</script>
    </body>
    </html>
    

    기존과 변경된 부분은 36~45번째 줄에 첨부파일을 보여주는 부분이 추가된 것이다. 

    소스를 살펴보면 기존에 게시판 목록을 보여주는것과 다른점이 없다. 

    그리고 파일이름을 클릭했을 때 첨부파일을 다운로드 할 수 있도록 파일이름에 클릭 이벤트를 바인딩 해놓은것을 볼 수 있다.(67~69번 줄)


    이제 이렇게 작성하고 소스를 실행시켜 보자.


    위와같이 지난글에서 첨부했던 파일의 이름과 파일크기를 정상적으로 보여주는 것을 볼 수 있다.

    만약 첨부파일이 없을 경우, "첨부파일이 없습니다." 와 같은 메시지를 보여주고 싶으면 <c:forEach> 구문안에 분기문을 작성해서 따로 처리해주면 된다.

    게시글 목록인 boardList.jsp에서도 한번 이야기를 했었기 때문에 따로 설명하지 않겠다.

    이제 앞에서 이야기한거 다시 이야기 안해도 될 때 아닌가?


    그럼 이제 첨부파일을 다운로드 하는 방법을 살펴보자.


    2. 첨부파일 다운로드

    1. JSP

    먼저 방금 작성한 boardDetail.jsp에서 파일 이름을 클릭할 때, 해당 첨부파일을 다운로드 하는 주소로 이동시키도록 수정하자.

    boardDetail.jsp의 스크립트 부분을 다음과 같이 수정하자.

    <script type="text/javascript">
    	$(document).ready(function(){
    		$("#list").on("click", function(e){ //목록으로 버튼
    			e.preventDefault();
    			fn_openBoardList();
    		});
    		
    		$("#update").on("click", function(e){ //수정하기 버튼
    			e.preventDefault();
    			fn_openBoardUpdate();
    		});
    		
    		$("a[name='file']").on("click", function(e){ //파일 이름
    			e.preventDefault();
    			fn_downloadFile($(this));
    		});
    	});
    	
    	function fn_openBoardList(){
    		var comSubmit = new ComSubmit();
    		comSubmit.setUrl("<c:url value='/sample/openBoardList.do' />");
    		comSubmit.submit();
    	}
    	
    	function fn_openBoardUpdate(){
    		var idx = "${map.IDX}";
    		var comSubmit = new ComSubmit();
    		comSubmit.setUrl("<c:url value='/sample/openBoardUpdate.do' />");
    		comSubmit.addParam("IDX", idx);
    		comSubmit.submit();
    	}
    	
    	function fn_downloadFile(obj){
    		var idx = obj.parent().find("#IDX").val();
    		var comSubmit = new ComSubmit();
    		comSubmit.setUrl("<c:url value='/common/downloadFile.do' />");
    		comSubmit.addParam("IDX", idx);
    		comSubmit.submit();
    	}
    </script>
    

    첨부파일의 파일명을 클릭하면 fn_downloadFile 이라는 함수가 실행되도록 하였다.

    fn_downloadFile 함수에서는 해당 파일의 IDX값을 가져와서 /common/downloadFile.do 라는 주소로 submit을 하는것을 알 수 있다.

    클라이언트, 즉 화면에서는 이렇게만 하면 된다. 다음으로는 서버측을 살펴보자.


    2. Java 및 SQL

    먼저 src/main/java 밑의 first/common 패키지 밑에 공통기능을 수행할 공통Controller, Service, DAO를 생성하자.

    controller, service 패키지를 생성하고 CommonController, CommonService, CommonServiceImpl, CommonDAO를 생성한다.

    그 후, src/main/resources 밑의 mapper 폴더 밑에 공통쿼리를 작성할 Common_SQL 파일을 생성하자.

    common 폴더 생성 후 Common_SQL.xml 파일을 생성한다.


    1) CommonController

    package first.common.controller;
    
    import javax.annotation.Resource;
    
    import org.apache.log4j.Logger;
    import org.springframework.stereotype.Controller;
    
    import first.common.service.CommonService;
    
    @Controller
    public class CommonController {
    	Logger log = Logger.getLogger(this.getClass());
    	
    	@Resource(name="commonService")
    	private CommonService commonService;
    }
    
    

    2) CommonService

    package first.common.service;
    
    public interface CommonService {
    
    }
    


    3) CommonServiceImpl

    package first.common.service;
    
    import javax.annotation.Resource;
    
    import org.apache.log4j.Logger;
    import org.springframework.stereotype.Service;
    
    import first.common.dao.CommonDAO;
    
    @Service("commonService")
    public class CommonServiceImpl implements CommonService{
    	Logger log = Logger.getLogger(this.getClass());
    	
    	@Resource(name="commonDAO")
    	private CommonDAO commonDAO;
    }
    


    4) CommonDAO

    package first.common.dao;
    
    import org.springframework.stereotype.Repository;
    
    @Repository("commonDAO")
    public class CommonDAO extends AbstractDAO{
    
    }
    

    5) Common_SQL.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="common">
    
    </mapper>
    


    여기까지 완료되면 다음과 같은 구조를 가진다.

    이제 하나씩 작성을 하자. 

    먼저 첨부파일을 다운로드 하는 방식을 살펴보자.

    여기서는 다음과 같은 과정을 거쳐서 다운로드 기능을 작성할 것이다.


    화면에서 특정 첨부파일 다운로드 요청 -> 서버에서 해당 첨부파일 정보 요청 -> DB에서 파일정보 조회 -> 조회된 데이터를 바탕으로 클라이언트에 다운로드가 가능하도록 데이터 전송


    먼저 "화면에서 특정 첨부파일 다운로드 요청" 부분은 위에서 작성을 완료하였다. 


    이제 서버측의 동작을 살펴봐야하는데, 원래는 Controller부터 작성을 해야겠지만 Controller에서는 설명할 부분이 좀 있어서 별다른 부분이 없는 Service, DAO, SQL을 먼저 작성한 후 Controller를 살펴보도록 하겠다.


    다음의 소스를 작성하자.

    1) CommonService

    Map<String, Object> selectFileInfo(Map<String, Object> map) throws Exception;
    


    2) CommonServiceImpl

    @Override
    public Map<String, Object> selectFileInfo(Map<String, Object> map) throws Exception {
    	return commonDAO.selectFileInfo(map);
    }
    


    3) CommonDAO

    @SuppressWarnings("unchecked")
    public Map<String, Object> selectFileInfo(Map<String, Object> map) throws Exception{
    	return (Map<String, Object>)selectOne("common.selectFileInfo", map);
    }
    


    4) Common_SQL

    <select id="selectFileInfo" parameterType="hashmap" resultType="hashmap">
    	<![CDATA[
    		SELECT
    			STORED_FILE_NAME,
    			ORIGINAL_FILE_NAME
    		FROM
    			TB_FILE
    		WHERE
    			IDX = #{IDX}
    	]]>
    </select>
    

    특별한 부분은 없는것을 알 수 있다.

    쿼리에서는 저장된 파일명과 원본 파일명 두가지를 모두 조회하는것만 살펴보면 된다.


    이제 Controller를 작성할 차례이다.

    Controller에 다음의 소스를 작성하자.

    @RequestMapping(value="/common/downloadFile.do")
    public void downloadFile(CommandMap commandMap, HttpServletResponse response) throws Exception{
    	Map<String,Object> map = commonService.selectFileInfo(commandMap.getMap());
    	String storedFileName = (String)map.get("STORED_FILE_NAME");
    	String originalFileName = (String)map.get("ORIGINAL_FILE_NAME");
    	
    	byte fileByte[] = FileUtils.readFileToByteArray(new File("C:\\dev\\file\\"+storedFileName));
    	
    	response.setContentType("application/octet-stream");
    	response.setContentLength(fileByte.length);
    	response.setHeader("Content-Disposition", "attachment; fileName=\"" + URLEncoder.encode(originalFileName,"UTF-8")+"\";");
    	response.setHeader("Content-Transfer-Encoding", "binary");
    	response.getOutputStream().write(fileByte);
    	
    	response.getOutputStream().flush();
    	response.getOutputStream().close();
    }
    

    이제 소스를 하나씩 살펴보자.

    먼저, 메서드의 파라미터로 기존에 보지 못했던 HttpServletResponse response 라는것이 추가되었다.

    기존에 첨부파일을 업로드 할때는 HttpServletRequest request가 추가되었었는데, 이번에는 response가 추가되었다.

    화면에서 서버로 어떤 요청을 할때는 request가 전송되고, 반대로 서버에서 화면으로 응답을 할때는 response에 응답내용이 담기게 된다.


    위에서 "조회된 데이터를 바탕으로 클라이언트에 다운로드가 가능하도록 데이터 전송" 라는 과정이 있다고 이야기를 했었다. 여기서 다운로드가 가능한 데이터 전송이라는 것은 파일정보를 response에 담아주는것을 의미한다.


    3번째 줄의 commonService.selectFileInfo를 통해서 첨부파일의 정보를 가져온다.

    그 후, 실제로 파일이 저장된 위치에서 해당 첨부파일을 읽어서 byte[] 형태로 변환을 해야한다. 

    7번째 줄이 그 역할을 수행하는데, FileUtils 클래스는 기존에 우리가 만들었던 클래스가 아니라 org.apache.commons.io 패키지의 FileUtils 클래스이다.

    지난글에서 pom.xml에 commons-io와 commons-fileupload dependency를 추가했었다. (http://addio3305.tistory.com/83)

    그때 추가했던 라이브러리를 사용하는 것이다. 

    이 라이브러리를 사용하지 않더라도 파일을 읽어와서 byte[] 형식으로 변환할 수는 있지만, 그러면 상당히 복잡한 과정을 거쳐야한다. 

    읽어오는 파일의 위치는 "C:\\dev\\file\\" 에 저장된 파일이름을 붙이고 있다.

    앞에서 파일을 저장하는 위치를 C:\\dev\\file\\로 했었던 것을 기억하자. 거기에 저장된 파일명을 붙여서 가져오도록 하였다.


    그 다음으로 중요한 부분이 9~16번째 줄이다.

    읽어들인 파일정보를 화면에서 다운로드 할 수 있도록 변환을 하는 부분이다.

    우리가 인터넷을 통해서 데이터를 전송하면 request나 response에는 전송할 데이터 뿐만이 아니라 여러가지 정보가 담겨있다. 

    그 정보를 설정해 주는 부분을 9~13번째 줄에서 하는 것이다.

    해당 정보의 자세한 설명은 하지 않도록 하겠다. 


    여기서 꼭 확인해야 할것은 11번째 줄이다. 

    response.setHeader에 "Content-Disposition"이라는 속성을 지정하는 부분이다. 여기서 "attachment"로 설정하고 있다. 이는 첨부파일을 의미한다.

    기존에 첨부파일을 전송할 때 패킷을 분석해보면, request의 Content-Disposition 부분은 "multipart-form/data"로 설정이 되어있다. 

    즉 Content-Disposition 속성을 이용하여 해당 패킷이 어떤 형식의 데이터인지 알 수 있다. 


    그 다음으로 fileName=\"" + URLEncoder.encode(originalFileName,"UTF-8")+"\";" 부분이 있다.

    이것은 첨부파일의 이름을 지정해주는 역할을 수행한다. 

    우리가 파일을 다운로드 받으려고 하면, 파일을 저장할 위치를 선택하는 창이 뜨고 파일의 이름이 지정되어있는데, 이 부분이 그 역할을 수행한다.

    잠시후 결과화면에서 무슨말인지 다시 확인하도록 하겠다.

    여기서 중요한것은 첨부파일을 다운로드 할때, UTF-8로 인코딩 하는 것을 봐야한다.

    파일명이 한글인데 UTF-8로 인코딩하지 않으면 파일명이 깨지게 된다. 

    인터넷에서 첨부파일을 다운받을 때, 아주 가끔 이상한 파일명으로 저장된 기억이 있을것이다. 그게 바로 UTF-8로 인코딩이 되지 않을때이다.


    인터넷에서 첨부파일을 다운로드 하는 소스는 쉽게 찾아볼 수 있다. 그런데 필자가 예전에 진행했던 프로젝트의 경우, 다운로드가 되지 않거나 파일명이 깨지는 경우가 많았다. 위 소스는 필자의 환경에서는 정확히 동작했지만, 혹시라도 문제가 생긴다면 이야기 해주기를 바란다.


    여기서 소스를 작성할 때, 띄어쓰기, 대소문자 구분 등도 주의해야한다. 

    잘 살펴보면 "attachment; fileName" 사이에 띄어쓰기가 되어있다. 필자의 경우, 저 띄어쓰기를 안해서 다운로드 기능이 안되기도 했었다.

    그 외에도 \"" 부분도 꼭 붙여줘야지 다운로드가 가능했었다. 

    기능을 구현할 때 그런 부분을 놓치지 않고 꼭 확인하길 바란다.


    그 외에 15~16번째 줄에서 보듯이 response를 정리하고 닫아주는 것을 잊지말자. 


    이제 실행시켜서 파일이 다운로드가 되는지 확인해보자.



    먼저, 첨부파일 이름을 클릭하면 위에서 보는것과 같이 파일을 저장할 수 있는 창이 뜬다.

    여기서 파일 이름이 1.PNG로 되어있는데, 위에서 Content-Disposition 속성에 fileName값을 지정한 것이 여기에 반영이 된 것이다. 

    만약 위에서 fileName을 다른 이름으로 한다면 첨부파일은 그 이름으로 저장된다. 


    그리고 파일을 저장하면 바탕화면에 1.PNG 파일이 새로 생성된다. (여기서 바탕화면에 1.PNG라는 이름으로 저장하였다.)


    이제 이클립스의 로그도 확인해보자.


    위에서 보는것과 같이 선택한 파일명의 정보를 정확히 조회하는것을 볼 수 있다.


    이렇게해서 첨부파일의 다운로드는 완료되었다. 


    ------------------------------------------------------------------------------------


    여기까지 해서 첨부파일의 다운로드에 대한 설명도 끝이 났습니다.


    지금 여기서 제가 설명한 방법은 파일 다운로드의 기초입니다. 실제로 여기서는 예외처리나 보안에 관련된 문제는 하나도 신경쓰지 않았습니다.


    또한 파일의 정보를 가져오는것도 프로젝트별로 다를 수 있습니다.


    실제로 제가 예전에 참여했던 프로젝트에서는 첨부파일을 서버에 물리적인 파일을 생성하지 않고 DB에 BLOB으로 저장한 경우도 있었고, 


    물리적인 파일을 파일관리 솔루션을 이용하여 관리한 경우도 있었습니다. 


    따라서 진행하는 프로젝트에서 파일을 어떻게 관리할 것인지에 따라서 세부적인 구현은 달라져야합니다.


    여기서 설명한 내용을 바탕으로 프로젝트의 상황에 맞춰서 파일의 데이터를 가져오면 됩니다.


    그리고 파일의 수정에 관련된 부분은 다음 포스팅에서 한번에 다룰 예정입니다. 


    다중첨부파일을 등록하는것과 파일을 수정하는것을 합쳐서 이야기 하겠습니다. 


    first.zip





    댓글 74

    • 이전 댓글 더보기
    • 아무것도 모르는 초보자 2015.09.04 12:30

      와~ 해결했어요 ㅎㅎ. 이 곳에 와서 진짜 많은 걸 배웁니다 감동이에요 ㅠㅠ
      정말 감사드리고 앞으로도 힘내시고 좋은 지식 알려주세요!~
      감사합니다~

      • Favicon of https://addio3305.tistory.com BlogIcon 카루시에라 2015.09.07 15:46 신고

        가능하면 문제없이 설명을 해드리고 싶은데, 아무리 처리를 해줘도 저런 버그가 생기는건 어쩔수가 없네요 ㅡ.ㅜ

      • 우왕 2015.11.11 10:50

        저도 초보라서 그러는데 [아무것도 모르는 초보자]님과 같은 현상입니다. 자세히좀 해결방법을 알려주시면 안될까요?ㅠ
        잘 감이 안잡힙니다ㅠ key를 가진태그를 삭제한다는 말이랑
        생성된 컨트롤러에서 해당값이 배열인지 아닌지 검사해서
        하나의 값만 추가하는 거랑요ㅠㅠ

    • boooo 2015.10.19 10:48

      안녕하세요!! 지금 이렇게 해보니깐, 컨트롤러에서 downloadFile.do를 입력하는 부분에서 3번째 줄에 Map<String,Object> map = forwizBoardService.selectFileInfo(commandMap.getMap()); 여기에서 getMap부분이 오류가 나서요 ㅠㅠ오류명은 The method getMap() is undefined for the type CommandMap 이렇게 뜨는데 어떻게 해결하면 되나요...?

    • 글쎄 2015.10.23 15:55

      "아무것도 모르는 초보자"님과 같은 문제가 발생하네요..addParam이 어디 있는건지요..그저 기초없이 따라왔는데..여기서 스스로 해결할 능력이 없네요..에고..

      • Favicon of https://addio3305.tistory.com BlogIcon 카루시에라 2015.11.09 17:37 신고

        기존 글에서 ComSubmit을 만드는 부분에 addParam이 있습니다.

      • 지나가던사람 2015.11.27 10:55

        function fn_downloadFile(obj){
        var idx = obj.parent().find("#IDX";).val();
        var comSubmit = new ComSubmit();
        comSubmit.setUrl("<c:url value='/common/downloadFile.do' />";);

        if(gfn_isNull($("[name='IDX']";).val())==false){
        $("[name='IDX']";).remove();
        }
        comSubmit.addParam("IDX", idx);

        comSubmit.submit();
        }

        이렇게바꿔보세요

      • Favicon of https://addio3305.tistory.com BlogIcon 카루시에라 2015.11.27 23:16 신고

        해당 문제를 수정한 내용을 조만간 작성할 예정입니다.

      • ㄴㅇㄹ 2017.01.09 10:57

        지나가던 사람님 감사합니다!
        덕분에 오류 해결 하였습니다.

    • 안녕하세요 2015.11.25 16:08

      안녕하세요 이번에도 올려주신 글 실습하면서 많이 공부하고 있습니다.

      한가지 궁금한게 있는데

      CommonController에서 response 객체를 다루는데 어떤 시점에서 browser로 response 객체가 던져지는지 궁금합니다.

      예시로 SampleController에서는 오브젝트값을 browser로 던져줄때 return mv; 로 던지는데 response는 따로 전달하는

      구문이 없는거 같아 질문드립니다.

      • Favicon of https://addio3305.tistory.com BlogIcon 카루시에라 2015.11.27 23:14 신고

        mv안에 response값이 들어있습니다. 기본적으로는 Request와 Response를 사용하는데 mv 안에 reponse의 기능도 들어있다고 생각하시면 됩니다. 그렇지만 Controller에서 response 객체를 받아서 따로 설정해주는것도 가능합니다.

    • 감사합니다. 2015.12.29 10:57

      참조했습니다. 감사합니다.

    • BJChoi 2016.01.06 13:52

      안녕하세요 지금 ibatis로 하고있는데요 상세정보할때 뷰딴에서 ${map}이런식으로하면 전체가 들어오긴하는데
      ${map.idx} 이런식으로하면 하나하나값은 안들어오네요ㅜ

    • 몽몽이 2016.02.04 13:54

      크롬에서 다운로드 할때, 저런식으로 다운로드 창이 안뜨고 바로 다운이 되는 이유는 뭘까요ㅜ..; 다운로드 창으로 보여주는 옵션같은게 따로존재하는건가요.

    • 갓래머 2016.02.17 14:11

      질문있습니다..
      다운로드 부분에서 saveFileName 을 뽑아와서 fileByte 에 넣어주고 파일경로에있는 파일까지도 읽어오는데 ..
      Null ModelAndView returned to DispatcherServlet with name 'action': assuming HandlerAdapter completed request handling
      오류도 아닌것이 이런문구가 콘솔에 찍히더라구요
      그다음 아무 반응이없습니다... 화면에도 그렇구요
      무슨문제인가요 .. ?

      • 와니 2016.02.18 21:00

        혹시 ModelAndView 구문이 콘트롤러에 들어간거 아닌가요?
        다운로드에는 ModelAndView 구문이 필요없습니다.

    • 히유우 2016.03.10 15:08

      파일 다운로드 시 파일명에 공백이 있는경우 공백이 "+"가 되어 다운로드 되네요.
      저같은 경우는 파일명 encoding하는 부분을 "URLEncoder.encode(originalFileName, "UTF-8";).replace("+", "%20";)"
      이런식으로 바꿧더니 잘 되네요~ 혹시 다른 방법이 있나요?
      어랍 우괄호가 왜 스마일로 바뀌지;;;;;

    • 초보자 2016.04.28 11:19

      Map fileMap = testBoardService.downloadFile(map);
      System.out.println("fileMap ==> " + fileMap);

      String fileNm = fileMap.get("FILE_NM";).toString();
      String stoFileNm = fileMap.get("STO_FILE_NM";).toString();
      String filePath = fileMap.get("FILE_PATH";).toString();

      byte fileByte[] = FileUtils.readFileToByteArray(new File("C:\\Users\\Administrator\\Desktop\\com.hnt.template\\src\\main\\webapp\\upload\\"+stoFileNm));
      System.out.println("fileByteaa ==> " + fileByte.length);

      response.setContentType("application/octet-stream";);
      response.setContentLength(fileByte.length);
      response.setHeader("Content-Disposition", "attachment; fileName=\"" + URLEncoder.encode(fileNm,"UTF-8";)+"\";";);
      response.setHeader("Content-Transfer-Encoding", "binary";);
      response.getOutputStream().write(fileByte);
      System.out.println("1111";);

      response.getOutputStream().flush();
      response.getOutputStream().close();
      System.out.println("2222";);

      보기와 같이 작성 하였습니다.
      그런데 view에서 다운 받을 시 아무 반응이 없네요.
      혹시, 뭐가 문제인지 아시는가요? 고수님들 답변 부탁드립니다.
      (설명과 다르게, common을 controller에서 작업했으며, 테이블도 다르지만, 파일정보를 다가져옵니다. 그리거, 제일아래 콘솔에서 2222 찍은데 까지 잘나오구요.)

      • 진짜초보자 2016.11.02 19:01

        boardDetail.jsp 문제일것 같습니다~
        저도 파일을 눌러도 반응없는 증상이 있었는데 해결했습니다 ^^

    • BlogIcon 코딩쪼렙 2016.11.23 15:12

      ㅠㅠ진짜 감사합니다.... ㅠㅠ매일 참고하고있어요..

    • Favicon of http://ㅁㄴ BlogIcon ㄴㅇㄹㄴㅇㄹ 2017.01.09 10:30

      첫번째 다운로드할때 KEY값은 잘 들어오는데,
      두번째 다운로드를 하려고 하니 KEY값이 Ljava.lang.String;@3d831929

      이런식으로 되어있네요 ㅠㅠ.. 어떻게 해야할까요

      • ㄴㅇㄹㄴㅇㄹ 2017.01.09 10:57

        2번째 들어올때 controller에 IDX값이 배열로 들어와서 생기는 문제였습니다.

        지나가던사람 님 댓글을 참조하여 수정하였더니 잘 실행 됩니다.


        function fn_downloadFile(obj){
        var idx = obj.parent().find("#IDX";).val();
        var comSubmit = new ComSubmit();
        comSubmit.setUrl("<c:url value='/common/downloadFile.do' />";);

        if(gfn_isNull($("[name='IDX']";).val())==false){
        $("[name='IDX']";).remove();
        }
        comSubmit.addParam("IDX", idx);

        comSubmit.submit();
        }

        이렇게바꿔보세요

        출처: http://addio3305.tistory.com/84 [흔한 개발자의 개발 노트]

    • 뒹구르르 2017.01.24 15:04

      정말 많은 공부가 되었습니다. 감사합니다.

    • 새벽 두시 2017.03.17 14:24

      어제 3시간 삽질했는데 알고 봤더니 다운로드 파일 위치를 제가 변경안했더군요 ^^;; 암튼 감사합니다!

    • 왕초보개발자 2017.07.03 16:00

      첫번째 다운로드할때 KEY값은 잘 들어오는데,
      두번째 다운로드를 하려고 하니 KEY값이 Ljava.lang.String;@3d831929

      function fn_downloadFile(obj){
      var idx = obj.parent().find("#IDX";).val();
      var comSubmit = new ComSubmit();
      $("#commonForm";).children().remove(); <<( 이부부만 추가함)
      comSubmit.setUrl("<c:url value='/common/downloadFile.do' />";);
      comSubmit.addParam("IDX", idx);
      comSubmit.submit();
      }

      저는 이렇게 해결했습니다. commonForm 안에 자식 태그들을 삭제하여 기존에 있던 태그 초기화후 전송하면
      두개 이상이 들어가지 않습니다.

    • 랄라 2017.08.10 17:39

      MYSQL 하시는 분들을 위해서 혹시 계속 IDX의 NULL값때문에 안되는 분이 계시다면 FILE 테이블의 IDX에 AI 속성을 추가해주세요 그리고 인서트보드, 인서트파일 구문에 IDX항목을 쓰지 않으시면 됩니다...

    • asasd 2017.11.21 18:21

      MySQL IDX 값 문제는 저는 이렇게 해결했습니다
      LAST_INSERT_ID 라는게 있어서 키값의 AI 설정이 되어있는 테이블일때
      insertBoard 쿼리 안쪽에
      <selectKey keyProperty="IDX" resultType="string" order="AFTER">
      SELECT LAST_INSERT_ID() AS IDX;
      </selectKey>
      이걸 넣었더니 잘되더군여

    • 방문자 2018.06.03 13:23

      모바일에서 파일 다운로드(또는 파일 바로 띄우기)도 이 방식으로 가능한가요?

    • spring배우는사람 2018.06.11 01:07

      detail을 수정하고나면 수정기능과 삭제기능이 먹통이됩니다...ㅠㅠㅠㅠㅠ

      • 저두요 2018.11.08 14:06

        저랑 똑같은 상황이네요....
        저도 수정과 삭제 기능이 이상해 졌어요ㅠㅠ

      • 개린이 2018.12.13 15:14

        SampleController.java에서 openBoardUpdate 메소드에서
        mv.addObject("map", map.get("map";)); 로 변경해보세요.

    • 파비코 2018.11.19 21:45

      특수문자가 들어갔던 파일의 경우 다운받으면서 깨져서 나오길래
      Controller 처리 부분 중간에
      originalFileName = new String( originalFileName.getBytes( "UTF-8" ), "8859_1" );
      넣어주고 response.setHeader 부분에 URLEncoder 없이 하니까
      특수문자 깨지는건 없는데..
      이 처리는 톰캣이 8859_1 을 사용하기 때문에 가능하다고 하더라구요 ㅠㅠ
      혹시 다른 특수문자 처리 방법을 아시는 분 있나요 ㅠㅠㅠ

Designed by Tistory.