본문 바로가기

학원/복기

[Spring] 자료실 만들기

예제)

 

테이블, 시퀀스 생성 

테이블 생성 (파일 저장용)
create table fileboard(idx number primary key, writer varchar2(20), subject varchar2(100)
    , origin varchar2(100), upload varchar(100));

시퀀스 생성 (idx에 제공)
create sequence fileboard_seq;

 

이름      널?       유형            
------- -------- ------------- 
IDX     NOT NULL NUMBER        - 글번호 
WRITER           VARCHAR2(20)  - 작성자
SUBJECT          VARCHAR2(100) - 제목
ORIGIN           VARCHAR2(100) - 사용자로부터 입력받은 파일명
UPLOAD           VARCHAR2(100) - 서버에 저장된 파일명

 

 

DTO

FileBoard 

//DAO 클래스의 메소드에서 사용하기 위한 객체를 표현하기 위한 클래스 - DTO 클래스
// -> 전달값이 저장된 Command 객체를 표현하기 위한 클래스의 기능
@Data
public class FileBoard {
	private int idx;
	private String writer;
	private String subject;
	private String origin;
	private String upload;
	//사용자로부터 입력되어 전달된 파일정보를 저장하기 위한 필드
	private MultipartFile multipartFile;
}

 

Mapper

 

xml매퍼

 

interface 매퍼

public interface FileBoardMapper {
	int insertFileBoard(FileBoard fileBoard);
	int deleteFileBoard(int idx);
	FileBoard selectFileBoard(int idx);
	List<FileBoard> selectFileBoardList();
}

 

 

DAO

//DAO 클래스가 상속받을 인터페이스
public interface FileBoardDAO {
	int insertFileBoard(FileBoard fileBoard);
	int deleteFileBoard(int idx);
	FileBoard selectFileBoard(int idx);
	List<FileBoard> selectFileBoardList();
}

@Repository
@RequiredArgsConstructor
public class FileBoardDAOImpl implements FileBoardDAO {
	private final SqlSession sqlSession;

	@Override
	public int insertFileBoard(FileBoard fileBoard) {
		return sqlSession.getMapper(FileBoardMapper.class).insertFileBoard(fileBoard);
	}

	@Override
	public int deleteFileBoard(int idx) {
		return sqlSession.getMapper(FileBoardMapper.class).deleteFileBoard(idx);
	}

	@Override
	public FileBoard selectFileBoard(int idx) {
		return sqlSession.getMapper(FileBoardMapper.class).selectFileBoard(idx);
	}

	@Override
	public List<FileBoard> selectFileBoardList() {
		return sqlSession.getMapper(FileBoardMapper.class).selectFileBoardList();
	}
}

 

Service 

//서비스 클래스가 상속받을 인터페이스
public interface FileBoardService {
	void addFileBoard(FileBoard fileBoard);
	void removeFileBoard(int idx);
	FileBoard getFileBoard(int idx);
	List<FileBoard> getFileBoardList();
}

@Service
@RequiredArgsConstructor
public class FileBoardServiceImpl implements FileBoardService {
	private final FileBoardDAO fileBoardDAO;

	@Transactional(rollbackFor = Exception.class)
	@Override
	public void addFileBoard(FileBoard fileBoard) {
		fileBoardDAO.insertFileBoard(fileBoard);
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public void removeFileBoard(int idx) {
		/*
		if(fileBoardDAO.selectFileBoard(idx) == null) {
			throw new Exception();
		}
		*/
		fileBoardDAO.deleteFileBoard(idx);
	}

	@Override
	public FileBoard getFileBoard(int idx) {
		return fileBoardDAO.selectFileBoard(idx);
	}

	@Override
	public List<FileBoard> getFileBoardList() {
		return fileBoardDAO.selectFileBoardList();
	}
}

 

 

Controller

 

FileController

@RequestMapping(value = "/write", method = RequestMethod.GET)
public String fileBoardWrite() {
	return "file/board_write";
}

 

 

 

파일 저장 디렉토리 위치

 

 

클라이언트가 접근할 수 없도록 하기 위해 업로드 파일을 숨긴 것이다.

클라이언트가 직접 접근하도록 하고 싶으면 resources 폴더에 만들면 된다.

 

 

FileController

public class FileController {
	//WebApplicationContext 객체(스프링 컨테이너)를 제공받아 필드에 의존성 주입
	private final WebApplicationContext context;
	//FileBoardService 객체를 제공받아 필드에 의존성 주입
	private final FileBoardService fileBoardService;
    
        ...
        ...

	@RequestMapping(value = "/write", method = RequestMethod.GET)
	public String fileBoardWrite() {
		return "file/board_write";
	}
	
	@RequestMapping(value = "/write", method = RequestMethod.POST)
	public String fileBoardWrite(@ModelAttribute FileBoard fileBoard) throws IllegalStateException, IOException {
		if(fileBoard.getMultipartFile().isEmpty()) {//파일이 없는 경우
			return "file/board_write";
		}
		
		//전달파일을 저장하기 위한 서버 디렉토리의 시스템 경로를 반환받아 저장
		// -> 다운로드 프로그램에서만 파일에 접근 가능하도록 /WEB-INF 폴더에 업로드 폴더를 작성 (클라이언트 직접 접근 불가)
		String uploadDirectory=context.getServletContext().getRealPath("/WEB-INF/upload");
		
		//사용자로부터 전달받은 파일의 이름을 반환받아 Command 객체의 필드값을 변경
		String origin=fileBoard.getMultipartFile().getOriginalFilename();
		fileBoard.setOrigin(origin);
		
		//서버 디렉토리에 업로드 처리되어 저장된 파일의 이름을 반환받아 Command 객체의 필드값으로 저장
		// => 서버 디렉토리에 저장된 파일 이름은 중복되지 않도록 고유값을 사용
		// => 중복되지 않는 고유값을 시스템의 현재 날짜와 시간에 대한 정수값(TimeStamp)으로 사용
		String upload=System.currentTimeMillis()+"";//대신 UUID 사용해도 됨 
		fileBoard.setUpload(upload);
		
		//파일 업로드 처리
		fileBoard.getMultipartFile().transferTo(new File(uploadDirectory, upload));
		
		//FILEBOARD 테이블에 행 삽입
		fileBoardService.addFileBoard(fileBoard);
		
		return "redirect:/file/list";
	}
	
	@RequestMapping(value = "/list")
	public String fileBoardList(Model model) {
		model.addAttribute("fileBoardList", fileBoardService.getFileBoardList());
		return "file/board_list";
	}
}

 

 

board_write.jsp

<body>
	<h1>자료실(입력페이지)</h1>
	<hr>
	<form action="<c:url value="/file/write"/>" method="post" enctype="multipart/form-data">
	<table>
		<tr>
			<td>작성자</td>
			<td><input type="text" name="writer" value="${fileBoard.write }"></td>
		</tr>
		<tr>
			<td>제목</td>
			<td><input type="text" name="subject" value="${fileBoard.subject }"></td>
		</tr>
		<tr>
			<td>파일</td>
			<td><input type="file" name="multipartFile"></td>
		</tr>
		<tr>
			<td colspan="2"><button type="submit">파일전송</button></td>
		</tr>
	</table>
	</form>
</body>

 

board_list.jsp

<body>
	<h1>자료실(출력페이지)</h1>
	<hr>
	<table>
		<tr>
			<th width="50">번호</th>
			<th width="100">작성자</th>
			<th width="300">제목</th>
			<th width="350">파일명</th>
			<th width="100">다운로드</th>
			<th width="100">삭제</th>
		</tr>
		
		<c:forEach var="fileBoard" items="${fileBoardList }">
		<tr>
			<td align="center">${fileBoard.idx }</td>
			<td align="center">${fileBoard.writer }</td>
			<td>${fileBoard.subject }</td>
			<td>${fileBoard.origin }</td>
			<td align="center">
				<button type="button" onclick="fileDownload(${fileBoard.idx });">다운로드</button> 
			</td>
			<td align="center">
				<button type="button" onclick="fileDelete(${fileBoard.idx });">삭제</button> 
			</td>
		</tr>
		</c:forEach>
	</table>
	
	<p>
		<button type="button" onclick="location.href='<c:url value="/file/write"/>';">업로드</button>
	</p> 
	
	<script type="text/javascript">
	function fileDownload(idx) { 
		//URL 주소를 이용하여 자료실 번호 전달
		location.href="<c:url value="/file/download"/>?idx="+idx;
	}
	
	function fileDelete(idx) {
		if(confirm("자료를 정말로 삭제 하시겠습니까?")) {
			location.href="<c:url value="/file/delete"/>?idx="+idx;
		} 
	}
	</script>
</body>

 


삭제 기능 구현하기

 

FileController

@RequestMapping("/delete")
public String fileBoardDelete(@RequestParam int idx) {
	FileBoard fileBoard=fileBoardService.getFileBoard(idx);
	String uploadDirectory=context.getServletContext().getRealPath("/WEB-INF/upload");
	//서버 디렉토리에 저장된 업로드 파일을 삭제 처리
	new File(uploadDirectory, fileBoard.getUpload()).delete();
	fileBoardService.removeFileBoard(idx);
	return "redirect:/file/list";
}

 


다운로드 기능 구현하기

 

다운로드(Download)

: 서버 디렉토리에 존재하는 파일을 클라이언트에게 전달하여 저장하는 기능


요청 처리 메소드에 의해 반환되는 문자열(VeiwName)으로 다운로드 프로그램을 실행하여 서버 디렉토리에 저장된 파일을 클라이언트에게 전달되도록 응답 처리할 것이다.

  • BeanNameViewResolver 객체를 사용하여 반환되는 문자열(VeiwName)로 특정 프로그램을 실행하여 응답 처리할 것임
  • Spring Bean Configuration File(servlet-context.xml)에 BeanNameViewResolver 클래스를 Srping Bean으로 등록해야 함
  • 현재 사용중인 VeiwResolver 객체(JSP 문서 응답 처리)보다 먼저 실행될 수 있도록 우선권을 설정해야 함 

 

BeanNameViewResolver 객체는 요청 처리 메소드에서 반환되는 문자열(ViewName)을 제공받아 같은 Spring Bean(객체) 중 같은 이름의 식별자(beanName)의 Spring Bean(객체)로 실행 메소드를 호출하여 클라이언트에게 응답 처리할 수 있도록 해준다.

  • JSP 문서를 이용하여 응답 처리하지 않고 메소드의 명령을 실행하여 응답 처리할 수 있도록 한다.
  • JSP 문서로 응답 처리하는 ViewResolver 객체보다 반드시 우선순위를 높게 설정해야 한다.

 

servlet-context.xml

BeanNameViewResolver 클래스를 Spring Bean으로 등록

<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<beans:property name="order" value="1"/>
</beans:bean>

 

 

파일 다운로드 기능을 제공하기 위한 클래스 FileDownload를 작성해보자

 

 

  • FileDownload 클래스는 BeanNameResolver 객체에 의해 실행되는 클래스이며 spring Bean Configuration File(servlet-context.xml)에 Spring Bean으로 등록해주어야 한다.
  • BeanNameViewResolver 객체에 의해 실행될 클래스는 반드시 AbstractView 클래스를 상속받아 작성해야 한다.
    • renderMergedOutputModel() 메소드를 오버라이드 선언하여 응답 처리에 필요한 명령을 작성해주면 된다.

 

메소드 정리 

  • AbstractView.setContentType(String contentType) : AbstractView 객체에 저장될 클라이언트의 응답 파일형식(MimeType)을 변경하는 메소드
  • AbstractView.getContentType() : AbstractView 객체에 저장될 클라이언트의 응답 파일형식(MimeType)을 반환하는 메소드 
  • response.setContentLengthLong(long len) : 클라이언트에게 파일의 크기를 전달하는 메소드 
  • FileCopyUtils.copy(InputStream in, OutputStream out) : 입력스트림으로 원시데이터를 읽어 출력 스트림으로 전달하여 저장하는 메소드 - 복사

 

FileDowndoad 

public class FileDownload extends AbstractView {
	public FileDownload() {
		//클라이언트에게 응답될 파일형식(Mimetype)을 변경
		setContentType("application/download; utf-8");
	}
	
	
	//BeanNameViewResolver 객체에 의해 자동 호출되는 메소드(실행 메소드)
	// => model 매개변수에는 요청 처리 메소드(fileBoardDownload())에서 제공된 속성값이 엔트리로 저장된 Map 객체를 제공받아 사용
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		//요청 처리 메소드에서 제공된 속성값(다운로드 관련 파일 정보)을 객체로 반환받아 저장
		String uploadDirectroy=(String)model.get("uploadDirectroy");
		String originalFilename=(String)model.get("originalFilename");
		String uploadFilename=(String)model.get("uploadFilename");
		
		//서버 디렉토리에 저장된 업로드 파일에 대한 File 객체 생성
		File file=new File(uploadDirectroy, uploadFilename);
		
		//클라이언트에게 파일을 전달하여 저장하기 위한 파일형식(MimeType)을 클라이언트에게 전달 
		response.setContentType(getContentType());//AbstractView 객체에 저장될 클라이언트의 응답 파일형식을 반환받아 변경
		response.setContentLengthLong((int)file.length());//클라이언트에게 파일의 크기를 전달
		//클라이언트에게 저장될 파일명을 클라이언트에게 전달 
		// => 파일명에 대한 한글이 존재할 경우 부호화 처리하여 전달
		originalFilename=URLEncoder.encode(originalFilename, "utf-8");
		response.setHeader("Content-Disposition", "attachement;filename=\""+originalFilename+"\";");
		
		//파일을 클라이언트에게 전달하기 위한 파일 출력스트림을 반환받아 저장
		OutputStream out=response.getOutputStream();
		
		//서버 디렉토리에 저장된 업로드 파일을 읽기 위한 입력스트림을 생성하여 저장
		InputStream in=new FileInputStream(file);
		
		//입력스트림으로 원시데이터를 읽어 출력 스트림으로 전달하여 저장 (복사)
		FileCopyUtils.copy(in, out);//다운로드 처리
		 
		in.close();
	}
}

 

 

servlet-context.xml

파일 다운로드 기능을 제공하는 클래스를 Spring Bean으로 등록
-> Spring Bean의 식별자(beanName)를 반드시 요청 처리 메소드의 반환값과 같도록 작성해야함

<beans:bean class="xyz.itwill10.util.FileDownload" id="fileDownload"/>

 

FileController

	//BeanNameViewResolver 객체를 사용하여 반환되는 문자열(VeiwName)로 특정 프로그램을 실행하여 응답 처리
	@RequestMapping("/download")
	public String fileBoardDownload(@RequestParam int idx, Model model) {
		FileBoard fileBoard=fileBoardService.getFileBoard(idx);
			
		//Model 객체를 이용하여 실행될 프로그램(Spring Bean)에서 사용될 객체를 속성값으로 저장하여 제공
		model.addAttribute("uploadDirectory", context.getServletContext().getRealPath("/WEB-INF/upload"));
		model.addAttribute("originalFilename", fileBoard.getOrigin());
		model.addAttribute("uploadFilename", fileBoard.getUpload());
			
		//실행될 프로그램(Spring Bean)의 식별자(beanName) 반환
		// => 실행될 프로그램에 대한 클래스를 작성하여 Spring Bean Configuration File
		//(servlet-context.xml)에 Spring Bean으로 등록 - 어노테이션 사용 가능
		return "fileDownload";
	}

 


페이징 처리하기  (fileBoardList 메소드)

 

매퍼 수정

 

FileBoardMapper.xml (XML)

<!-- 
<select id="selectFileBoardList" resultType="FileBoard">
	select idx, writer, subject, origin, upload from fileboard order by idx desc	
</select>
 -->

<select id="selectFileBoardList" resultType="FileBoard">
	select * from (select rownum rn, board.* from (select idx, writer, subject, origin, upload 
		from fileboard order by idx desc) board) where rn between #{startRow} and #{endRow}
</select>
	
<select id="selectFileBoardCount" resultType="int">
	select count(*) from fileboard
</select>

 

FileBoardMapper.java (인터페이스)

public interface FileBoardMapper {
	int insertFileBoard(FileBoard fileBoard);
	int deleteFileBoard(int idx);
	FileBoard selectFileBoard(int idx);
	//List<FileBoard> selectFileBoardList();
	List<FileBoard> selectFileBoardList(Map<String,Object> map);
	int selectFileBoardCount();
}

 

DAO 변경

 

DAO (DAO 클래스가 상속받을 인터페이스)

public interface FileBoardDAO {
	int insertFileBoard(FileBoard fileBoard);
	int deleteFileBoard(int idx);
	FileBoard selectFileBoard(int idx);
	//List<FileBoard> selectFileBoardList();
	List<FileBoard> selectFileBoardList(Map<String,Object> map);
	int selectFileBoardCount();
}

 

DAOImpl (DAO 클래스)

...

@Override
	public List<FileBoard> selectFileBoardList(Map<String, Object> map) {
		return sqlSession.getMapper(FileBoardMapper.class).selectFileBoardList(map);
	}

	@Override
	public int selectFileBoardCount() {
		return sqlSession.getMapper(FileBoardMapper.class).selectFileBoardCount();
	}
    
...

 

 

Service 변경

 

인터페이스

//List<FileBoard> getFileBoardList();
Map<String, Object> getFileBoardList(int pageNum);

 

 

서비스 클래스

 

FileBoardServiceImpl

변경 전
    
@Override
public List<FileBoard> getFileBoardList() {
	return fileBoardDAO.selectFileBoardList();
}
	
	
변경 후 
//매개변수로 요청 페이지 번호를 전달받아 게시글 목록이 저장된 객체와 페이지 번호 관련 객체를
//Map 객체의 엔트리로 추가하여 반환하는 메소드
@Override
public Map<String, Object> getFileBoardList(int pageNum) {
	//FILEBOARD 테이블에 저장된 모든 게시글의 갯수를 검색하여 반환하는 DAO 클래스의 메소드 호출
	int totalBoard=fileBoardDAO.selectFileBoardCount();
	int pageSize=5;//하나의 페이지에 출력될 게시글의 갯수 저장
	int blockSize=5;//하나의 블럭에 출력될 페이지의 갯수 저장
			
	//Pager 클래스로 객체를 생성하여 저장 - 생성자 매개변수에 페이징 처리 관련 값을 전달
	// => Pager 객체 : 페이징 처리 관련 값이 저장된 객체
	Pager pager=new Pager(pageNum, totalBoard, pageSize, blockSize);
			
	//FileBoardDAO 클래스의 selectFileBoardList() 메소드를 호출하기 위해 매개변수에 전달하여
	//저장될 Map 객체(시작 행번호, 종료 행번호) 생성
	Map<String, Object> pageMap=new HashMap<String, Object>();
	pageMap.put("startRow", pager.getStartRow());
	pageMap.put("endRow", pager.getEndRow());
	List<FileBoard> fileBoardList=fileBoardDAO.selectFileBoardList(pageMap);
			
	//Controller 클래스에 반환되는 결과값을 제공하기 위한 Map 객체(시작 행번호, 종료 행번호) 생성
	Map<String, Object> resultMap=new HashMap<String, Object>();
	resultMap.put("pager", pager);
	resultMap.put("fileBoardList", fileBoardList);
			
	return resultMap;
}

 

 

Pager 클래스 선언 

 

//페이징 처리 관련 값을 필드에 저장하기 위한 클래스
@Data
public class Pager {
	//생성자를 이용하여 초기값을 전달받아 필드에 저장
	private int pageNum;//요청 페이지의 번호
	private int totalBoard;//전체 게시글의 갯수
	private int pageSize;//하나의 페이지에 출력될 게시글의 갯수
	private int blockSize;//하나의 블럭에 출력될 페이지 번호의 갯수

	//생성자로 초기화된 필드값을 계산하여 결과값을 필드에 저장
	private int totalPage;//전체 페이지의 갯수
	private int startRow;//요청 페이지에 출력될 게시글의 시작 행번호
	private int endRow;//요청 페이지에 출력될 게시글의 종료 행번호
	private int startPage;//현재 블럭에 출력될 시작 페이지 번호
	private int endPage;//현재 블럭에 출력될 종료 페이지 번호
	private int prevPage;//이전 블럭에 출력될 시작 페이지 번호
	private int nextPage;//다음 블럭에 출력될 시작 페이지 번호
	
	public Pager(int pageNum, int totalBoard, int pageSize, int blockSize) {
		super();
		this.pageNum = pageNum;
		this.totalBoard = totalBoard;
		this.pageSize = pageSize;
		this.blockSize = blockSize;
		
		calcPage();
	}
	
	//계산된 결과값을 필드에 저장하는 메소드 - 생성자에서 호출하여 사용
	private void calcPage() {
		totalPage=(int)Math.ceil((double)totalBoard/pageSize);
		if(pageNum<=0 || pageNum>totalPage) {
			pageNum=1;
		}
		
		startRow=(pageNum-1)*pageSize+1;
 		endRow=pageNum*pageSize;
		if(endRow>totalBoard) {
			endRow=totalBoard;
		}
		
		startPage=(pageNum-1)/blockSize*blockSize+1;
		endPage=startPage+blockSize-1;
		if(endPage>totalPage) {
			endPage=totalPage;
		}
		
		prevPage=startPage-blockSize;
		nextPage=startPage+blockSize;
	}
	
}

 

 

FileController 변경

변경전

@RequestMapping("/list")
public String fileBoardList(Model model) {
	model.addAttribute("fileBoardList", fileBoardService.getFileBoardList());
	return "file/board_list";
}

변경 후

@RequestMapping("/list")
public String fileBoardList(@RequestParam(defaultValue = "1") int pageNum,Model model) {
	//System.out.println("pageNum = "+pageNum);
		
	Map<String, Object> map=fileBoardService.getFileBoardList(pageNum);
		
	model.addAttribute("pager", map.get("pager"));
	model.addAttribute("fileBoardList", map.get("fileBoardList"));
	return "file/board_list";
}

 

 

board_list.jsp 에서 번호 출력되도록 수정

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SPRING</title>
<style type="text/css">
table {
	border: 1px solid black;
	border-collapse: collapse;
}

th, td {
	border: 1px solid black;
	padding: 2px;
}
</style>
</head>
<body>
	<h1>자료실(출력페이지)</h1>
	<hr>
	<p>
		<button type="button" onclick="location.href='<c:url value="/file/write"/>';">업로드</button>
	</p> 
	
	<table>
		<tr>
			<th width="50">번호</th>
			<th width="100">작성자</th>
			<th width="300">제목</th>
			<th width="350">파일명</th>
			<th width="100">다운로드</th>
			<th width="100">삭제</th>
		</tr>
		
		<%-- 게시글 목록 출력 --%>
		<c:forEach var="fileBoard" items="${fileBoardList }">
		<tr>
			<td align="center">${fileBoard.idx }</td>
			<td align="center">${fileBoard.writer }</td>
			<td>${fileBoard.subject }</td>
			<td>${fileBoard.origin }</td>
			<td align="center">
				<button type="button" onclick="fileDownload(${fileBoard.idx });">다운로드</button> 
			</td>
			<td align="center">
				<button type="button" onclick="fileDelete(${fileBoard.idx });">삭제</button> 
			</td>
		</tr>
		</c:forEach>
	</table>

	<%-- 페이지 번호 출력 --%>
	<c:choose>
		<c:when test="${pager.startPage > pager.blockSize }">
			<a href="<c:url value="/file/list"/>?pageNum=${pager.prevPage}">[이전]</a>
		</c:when>
		<c:otherwise>
			[이전]
		</c:otherwise>
	</c:choose>	
	
	<c:forEach var="i" begin="${pager.startPage }" end="${pager.endPage }" step="1">
		<c:choose>
			<c:when test="${pager.pageNum != i  }">
				<a href="<c:url value="/file/list"/>?pageNum=${i}">[${i }]</a>
			</c:when>
			<c:otherwise>
				[${i }]
			</c:otherwise>
		</c:choose>	
	</c:forEach>

	<c:choose>
		<c:when test="${pager.endPage != pager.totalPage }">
			<a href="<c:url value="/file/list"/>?pageNum=${pager.nextPage}">[다음]</a>
		</c:when>
		<c:otherwise>
			[다음]
		</c:otherwise>
	</c:choose>	
	
	<script type="text/javascript">
	function fileDownload(idx) { 
		//질의문자열를 이용하여 자료실 번호 전달
		location.href="<c:url value="/file/download"/>?idx="+idx;
	}
	
	function fileDelete(idx) {
		if(confirm("자료를 정말로 삭제 하시겠습니까?")) {
			location.href="<c:url value="/file/delete"/>?idx="+idx;
		} 
	}
	</script>
</body>
</html>