create table fileboard(num number primary key, writer varchar2(20) , subject varchar2(100), origin varchar2(100), upload varchar2(100));create sequence fileboard_seq;
📃FileBoard.java
※ xyz.itwill10.dto 패키지에 FileBoard.java 클래스 생성
package xyz.itwill10.dto; // import org.springframework.web.multipart.MultipartFile; import lombok.Data; /* 이름 널? 유형 ------- -------- ------------- NUM NOT NULL NUMBER - 글번호 WRITER VARCHAR2(20) - 작성자 SUBJECT VARCHAR2(100) - 제목 ORIGIN VARCHAR2(100) - 원본 파일명 UPLOAD VARCHAR2(100) - 업로드 파일명(서버 디렉토리에 저장되는 파일명) */ // //DTO 클래스 >> 전달값을 제공받아 사용하기 위한 Command 객체의 클래스 // @Data public class FileBoard { private int num; private String writer; private String subject; private String origin; private String upload; private MultipartFile file;//전달파일을 저장하기 위한 필드 }
📃FileBoardMapper.xml
※ xyz.itwill10.mapper 패키지에 FileBoardMapper.xml 파일 생성
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="xyz.itwill10.mapper.FileBoardMapper"> <!-- =============================================================================================== --> <insert id="insertFileBoard"> <selectKey resultType="int" keyProperty="num" order="BEFORE"> select fileboard_seq.nextval from dual </selectKey> insert into fileboard values(#{num},#{writer},#{subject},#{origin},#{upload}) </insert> <!-- =============================================================================================== --> <delete id="deleteFileBoard"> delete from fileboard where num=#{num} </delete> <!-- =============================================================================================== --> <select id="selectFileBoard" resultType="FileBoard"> select * from fileboard where num=#{num} </select> <!-- =============================================================================================== --> <select id="selectFileBoardList" resultType="FileBoard"> select * from fileboard order by num desc </select> </mapper>
📃FileBoardMapper.java(인터페이스)
※ xyz.itwill10.mapper 패키지에 FileBoardMapper.java 인터페이스 파일 생성
package xyz.itwill10.mapper; // import java.util.List; import xyz.itwill10.dto.FileBoard; // public interface FileBoardMapper { int insertFileBoard(FileBoard fileBoard); int deleteFileBoard(int num); FileBoard selectFileBoard(int num); List<FileBoard> selectFileBoardList(); }
📃FileBoardDAO.java(인터페이스)
※ xyz.itwill10.dao 패키지에 FileBoardDAO.java 인터페이스 생성
package xyz.itwill10.dao; // import java.util.List; import xyz.itwill10.dto.FileBoard; // public interface FileBoardDAO { int insertFileBoard(FileBoard fileBoard); int deleteFileBoard(int num); FileBoard selectFileBoard(int num); List<FileBoard> selectFileBoardList(); }
📃FileBoardDAOImpl.java
※ xyz.itwill10.dao 패키지에 FileBoardDAOImpl.java 클래스 생성
package xyz.itwill10.dao; // import java.util.List; import org.apache.ibatis.session.SqlSession; import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; import xyz.itwill10.dto.FileBoard; import xyz.itwill10.mapper.FileBoardMapper; // @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 num) { return sqlSession.getMapper(FileBoardMapper.class).deleteFileBoard(num); } @Override public FileBoard selectFileBoard(int num) { return sqlSession.getMapper(FileBoardMapper.class).selectFileBoard(num); } @Override public List<FileBoard> selectFileBoardList() { return sqlSession.getMapper(FileBoardMapper.class).selectFileBoardList(); } }
📃FileBoardService.java(인터페이스)
※ xyz.itwill10.service 패키지에 FileBoardService.java 인터페이스 파일 생성
package xyz.itwill10.service; // import java.util.List; import xyz.itwill10.dto.FileBoard; // public interface FileBoardService { void addFileBoard(FileBoard fileBoard); void removeFileBoard(int num); FileBoard getFileBoard(int num); List<FileBoard> getFileBoardList(); }
📃FileBoardServiceImpl.java
※ xyz.itwill10.service 패키지에 FileBoardServiceImpl.java 클래스 생성
package xyz.itwill10.service; // import java.util.List; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import xyz.itwill10.dao.FileBoardDAO; import xyz.itwill10.dto.FileBoard; // @Service @RequiredArgsConstructor public class FileBoardServiceImpl implements FileBoardService { private final FileBoardDAO fileBoardDAO; // @Override public void addFileBoard(FileBoard fileBoard) { fileBoardDAO.insertFileBoard(fileBoard); } @Override public void removeFileBoard(int num) { fileBoardDAO.deleteFileBoard(num); } @Override public FileBoard getFileBoard(int num) { return fileBoardDAO.selectFileBoard(num); } @Override public List<FileBoard> getFileBoardList() { return fileBoardDAO.selectFileBoardList(); } }
📃FileController.java
※ xyz.itwill10.controller 패키지에 FileController.java 클래스 생성
package xyz.itwill10.controller; // import java.io.File; import java.io.IOException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; import xyz.itwill10.dto.FileBoard; import xyz.itwill10.service.FileBoardService; // //파일 업로드 처리를 위한 환경설정 방법 //1.commons-fileupload 라이브러리를 프로젝트에 빌드 처리 - 메이븐 : pom.xml //2.Spring Bean Configuration File(servlet-context.xml)에 파일 업로드 기능을 제공하는 클래스를 Spring Bean으로 등록 //3.MultipartHttpServletRequest 객체를 사용하여 [multipart/form-data] 형태로 전달된 값 또는 파일 처리 // @Controller @RequiredArgsConstructor public class FileController { //WebApplicationContext 객체(Spring Container)를 제공받아 필드에 의존성 주입 private final WebApplicationContext context; private final FileBoardService fileBoardService;//테이블 연동 // @RequestMapping(value = "/upload", method = RequestMethod.GET) public String upload() { return "file/upload_form"; } /* //요청 처리 메소드에 MultipartHttpServletRequest 인터페이스로 매개변수를 선언하면 Front Controller에 의해 MultipartHttpServletRequest 객체를 제공받아 사용 가능 //MultipartHttpServletRequest 객체 : [multipart/form-data] 형식으로 전달된 값 또는 파일을 처리하기 위한 객체 @RequestMapping(value = "/upload", method = RequestMethod.POST) public String upload(MultipartHttpServletRequest request) throws IOException { //MultipartHttpServletRequest.getParameter(String name) : [multipart/form-data] 형식으로 전달된 값을 문자열(String 객체)로 반환하는 메소드 String uploaderName=request.getParameter("uploaderName"); // //MultipartHttpServletRequest.getFile(String name) : [multipart/form-data] 형식으로 전달된 파일을 MultipartFile 객체로 반환하는 메소드 //MultipartFile 객체 : 사용자로부터 입력되어 전달된 파일정보를 저장하기 위한 객체 MultipartFile uploadFile=request.getFile("uploadFile"); // //전달받은 파일에 대한 검증 작업 //MultipartFile.isEmpty() : MultipartFile 객체에 파일정보가 없는 경우 [false]를 반환하고 파일정보가 있는 경우 [true]를 반환하는 메소드 if(uploadFile.isEmpty()) { return "file/upload_fail"; } // //MultipartFile.getContentType() : MultipartFile 객체에 저장된 파일의 형식(MimeType)를 반환하는 메소드 System.out.println("파일 형식 = "+uploadFile.getContentType()); //MultipartFile.getBytes() : MultipartFile 객체에 저장된 파일정보를 byte 배열로 반환하는 메소드 System.out.println("파일 크기 = "+uploadFile.getBytes().length); // //전달파일을 저장하기 위한 서버 디렉토리의 시스템 경로를 반환받아 저장 String uploadDirectory=request.getServletContext().getRealPath("/resources/images/upload"); System.out.println("uploadDirectory = "+uploadDirectory); // //전달파일을 서버 디렉토리에 저장하기 위한 File 객체 생성 //File 객체 : 시스템에 존재하는 파일정보를 저장하기 위한 객체 //MultipartFile.getOriginalFilename() : 전달파일의 파일명을 반환하는 메소드 File file=new File(uploadDirectory, uploadFile.getOriginalFilename()); // //MultipartFile.transferTo(File file) : MultipartFile 객체에 저장된 파일정보를 File 객체에 저장된 파일정보로 전달하여 저장하는 메소드 - 업로드 처리 메소드 uploadFile.transferTo(file); // request.setAttribute("uploaderName", uploaderName); request.setAttribute("uploadFilename", uploadFile.getOriginalFilename()); // return "file/upload_ok"; } */ // //요청 처리 메소드에 매개변수를 작성하여 전달값과 전달파일을 제공받아 사용 가능 //→ 업로드 파일과 같은 이름의 파일이 서버 디렉토리에 존재할 경우 기존 파일 대신 업로드 //파일이 저장 - 덮어씌위기(OverWrite) //→ commons-fileupload 라이브러리에는 업로드 파일을 변경하는 기능의 클래스 미존재 //→ 업로드 파일과 같은 이름의 파일이 서버 디렉토리에 존재할 경우 업로드 파일명을 변경하는 명령 작성 필요 @RequestMapping(value = "/upload", method = RequestMethod.POST) public String upload(@RequestParam String uploaderName , @RequestParam MultipartFile uploadFile, Model model) throws IOException { if(uploadFile.isEmpty()) { return "file/upload_fail"; } // //전달파일을 저장하기 위한 서버 디렉토리의 시스템 경로를 반환받아 저장 //→ WebApplicationContext 객체(Spring Container)에게 ServletContext 객체를 제공받아 사용 String uploadDirectory=context.getServletContext().getRealPath("/resources/images/upload"); // //사용자로부터 입력되어 전달된 원본 파일명을 반환받아 저장 String originalFilename=uploadFile.getOriginalFilename(); // //서버 디렉토리에 저장하기 위한 파일정보가 저장된 File 객체 생성 File file=new File(uploadDirectory, originalFilename); // //서버 디렉토리에 저장될 파일명을 저장하기 위한 변수 //→ 초기값으로 사용자로부터 입력되어 전달된 원본 파일명을 저장 String uploadFilename=originalFilename; // //업로드 파일과 같은 이름의 파일이 서버 디렉토리에 존재할 경우 서버 디렉토리에 저장될 파일명을 구분하기 위한 식별자를 저장하기 위한 변수 int i=0; // //File.exists() : File 객체에 저장된 파일이 시스템이 존재하지 않을 경우 [false]를 반환하고 존재할 경우 [true]를 반환하는 메소드 while(file.exists()) {//업로드 파일과 같은 이름의 파일이 서버 디렉토리에 존재할 경우 반복 처리 i++; // //파일명과 확장자를 구분하기 위한 문자열(.)를 사용하여 위치값(Index)을 반환받아 저장 int index=originalFilename.lastIndexOf("."); // //원본 파일명을 이용하여 업로드 파일명 생성 uploadFilename=originalFilename.substring(0, index)+"_"+i+originalFilename.substring(index); // //서버 디렉토리에 저장될 파일명을 저장하기 위한 변수 file=new File(uploadDirectory, uploadFilename); } // uploadFile.transferTo(file); // model.addAttribute("uploaderName", uploaderName); model.addAttribute("originalFilename", originalFilename); model.addAttribute("uploadFilename", uploadFilename); // return "file/upload_ok"; } //============================================================================================================================ //테이블 연동 @RequestMapping(value = "/fileboard/write", method = RequestMethod.GET) public String fileBoardWrite() { return "file/board_write"; } // //매개변수를 작성하여 모든 전달값과 전달파일을 Command 객체로 제공받아 사용 가능 @RequestMapping(value = "/fileboard/write", method = RequestMethod.POST) public String fileBoardWrite(@ModelAttribute FileBoard fileBoard) throws IllegalStateException, IOException {//전달값을 반환받아 저장 if(fileBoard.getFile().isEmpty()) { return "file/board_write"; } // //전달파일이 저장될 서버 디렉토리의 시스템 경로를 반환받아 저장 //→ 다운로드 프로그램에서만 파일에 접근 가능하도록 /WEB-INF 폴더에 업로드 폴더 생성 String uploadDir=context.getServletContext().getRealPath("/WEB-INF/upload");//파일을 저장할 경로를 지정 // //사용자로부터 입력받아 전달된 원본파일의 이름을 반환받아 저장 String origin=fileBoard.getFile().getOriginalFilename(); // //서버 디렉토리에 저장된 업로드 파일명을 생성하여 저장 //→ 업로드 파일명은 서버 디렉토리에 존재하는 파일명과 중복되지 않도록 고유값 사용 //→ 업로드 파일명은 시스템의 현재 날짜와 시간에 대한 정수값(TimeStamp)를 사용하여 작성 String upload=System.currentTimeMillis()+""; // //Command 객체(FileBoard 객체)의 필드값 변경 fileBoard.setOrigin(origin); fileBoard.setUpload(upload); // //파일 업로드 처리 fileBoard.getFile().transferTo(new File(uploadDir, upload));//에러 떠넘기기 // //FILEBOARD 테이블에 행 삽입 fileBoardService.addFileBoard(fileBoard); // return "redirect:/fileboard/list"; } // @RequestMapping("/fileboard/list") public String fileBoardList(Model model) { model.addAttribute("fileBoardList", fileBoardService.getFileBoardList()); return "/file/board_list"; } // //URL 주소로 전달된 값을 @PathVariable 어노테이션을 사용하여 매개변수로 제공받아 사용 @RequestMapping("/fileboard/delete/{num}") public String fileBoardDelete(@PathVariable int num) { FileBoard fileBoard=fileBoardService.getFileBoard(num); String uploadDir=context.getServletContext().getRealPath("/WEB-INF/upload"); //서버 디렉토리에 저장된 업로드 파일을 삭제 처리 new File(uploadDir, fileBoard.getUpload()).delete(); fileBoardService.removeFileBoard(num); return "redirect:/fileboard/list"; } // //다운로드(Download) : 서버 디렉토리에 존재하는 파일을 클라이언트에 전달하여 저장하는 기능 요청 처리 메소드에 의해 // 반환되는 문자열(ViewName)으로 다운로드 프로그램을 실행하여 서버 디렉토리에 저장된 파일을 클라이언트에게 전달하여 응답 처리 //→ BeanNameViewResolver 객체를 사용하여 반환하는 문자열로 특정 프로그램 실행 //→ Spring Bean Configuration File(servlet-context.xml)에 BeanNameViewResolver 클래스를 Spring Bean으로 등록 //현재 사용중인 ViewResolver 객체는 요청 처리 메소드에서 반환되는 문자열을 이용해 JSP 문서로 응답되도록 처리 //→ UrlBasedViewResolver(TilesView), InternalResourceViewResolver @RequestMapping("/fileboard/download/{num}") public String fileBoardDownload(@PathVariable int num, Model model) { FileBoard fileBoard=fileBoardService.getFileBoard(num); // //Model 객체를 이용하여 실행될 프로그램(Spring Bean)에서 사용하기 위한 객체를 속성값으로 저장하여 제공 model.addAttribute("uploadDir", context.getServletContext().getRealPath("/WEB-INF/upload")); model.addAttribute("uploadFilename",fileBoard.getUpload()); model.addAttribute("originalFilename",fileBoard.getOrigin()); // //실행될 프로그램(Spring Bean)의 식별자(beanName)를 반환 //→ 실행될 프로그램에 대한 클래스를 작성하여 Spring Bean Configuration File //(servlet-context.xml)에 Spring Bean으로 등록 return "fileDownload"; } }
📃board_write.jsp
※ WEB-INF/views/rest 폴더에 board_write.jsp 작성
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>SPRING</title> </head> <body> <h1>자료실(입력페이지)</h1> <hr> <form method="post" enctype="multipart/form-data"> <table> <tr> <td>작성자</td> <td><input type="text" name="writer"></td> </tr> <tr> <td>제목</td> <td><input type="text" name="subject"></td> </tr> <tr> <td>파일</td> <td><input type="file" name="file"></td> </tr> <tr> <td colspan="2"><button type="submit">파일전송</button></td> </tr> </table> </form> </body> </html>
📃board_list.jsp
※ WEB-INF/views/rest 폴더에 board_write.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> <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.num }</td> <td align="center">${fileBoard.writer }</td> <td>${fileBoard.subject }</td> <td>${fileBoard.origin }</td> <td align="center"> <button type="button" onclick="fileDownload(${fileBoard.num });">다운로드</button> </td> <td align="center"> <button type="button" onclick="fileDelete(${fileBoard.num });">삭제</button> </td> </tr> </c:forEach> </table> <p> <button type="button" onclick="location.href='${pageContext.request.contextPath}/fileboard/write';">업로드</button> </p> <!-- 다운로드 관련 함수 --> <script type="text/javascript"> function fileDownload(num) { //URL 주소를 이용하여 자료실 번호 전달 location.href="${pageContext.request.contextPath}/fileboard/download/"+num; } <!-- 삭제 관련 함수 --> function fileDelete(num) { if(confirm("자료를 정말로 삭제 하시겠습니까?")) { location.href="${pageContext.request.contextPath}/fileboard/delete/"+num; } } </script> </body> </html>
📃FileDownload.java
※ xyz.itwill10.util 패키지에 FileDownload.java 클래스 생성
package xyz.itwill10.util; // import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.FileCopyUtils; import org.springframework.web.servlet.view.AbstractView; // //파일 다운로드 기능을 제공하기 위한 클래스 - BeanNameViewResolver 객체에 의해 실행되는 클래스 //→ Spring Bean Configuration File(servlet-context.xml)에 Spring Bean으로 등록 //BeanNameViewResolver 객체에 의해 실행되는 클래스는 반드시 AbstractView 클래스를 상속받아 작성 //→ renderMergedOutputModel() 메소드를 오버라이드 선언하여 응답에 필요한 명령 작성 public class FileDownload extends AbstractView { //클라이언트에게 응답될 파일형식(MimeType)에 대한 변경 - 파일 다운로드 기능 public FileDownload() { //AbstractView.setContentType(String mimeType) : 응답 파일 형식을 변경하는 메소드 setContentType("application/download; utf-8"); } // //BeanNameViewResolver 객체에 의해 자동 호출되는 메소드 //→ Map 자료형의 매개변수에는 요청 처리 메소드에서 제공된 속성값이 엔트리로 저장된 Map 객체 제공 @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //요청 처리 메소드에서 제공된 속성값(파일 관련 정보)을 객체로 반환받아 저장 String uploadDir=(String)model.get("uploadDir"); String uploadFilename=(String)model.get("uploadFilename"); String originalFilename=(String)model.get("originalFilename"); // //서버 디렉토리에 저장된 다운로드 파일에 대한 File 객체 생성 File downloadFile=new File(uploadDir, uploadFilename); // //클라이언트에게 파일을 전달하기 위해 리스폰즈 메세지의 머릿부 변경 response.setContentType(getContentType()); response.setContentLengthLong((int)downloadFile.length()); // //다운로드 처리되어 클라이언트에 저장되 파일명을 리스폰즈 메세지의 머릿부에 저장하여 전달 //→ 파일명에 대한 한글 문제로 인해 부호화 처리하여 전달 originalFilename=URLEncoder.encode(originalFilename, "utf-8"); response.setHeader("Content-Disposition","attachement;filename=\""+originalFilename+"\";"); // //리스폰즈 메세지 몸체부에 파일을 전달하여 저장하기 위한 출력스트림을 반환받아 저장 OutputStream out=response.getOutputStream(); // //서버 디렉토리에 저장된 다운로드 파일을 읽기 위한 입력스트림을 생성하여 저장 InputStream in=new FileInputStream(downloadFile); // //FileCopyUtils.copy(InputStream in, OutputStream out) : 입력스트림으로 원시데이타를 반복적으로 읽어 출력스트림으로 전달하는 메소드 - 파일 복사 FileCopyUtils.copy(in, out); // in.close(); } }
📃servlet-context.xml
<!-- BeanNameViewResolver 클래스를 Spring Bean으로 등록 --> <!-- BeanNameViewResolver : 요청 처리 메소드에서 반환되는 문자열(ViewName)을 제공받아 같은 이름(beanName)의 객체(Spring Bean)의 실행 메소드를 호출하여 클라이언트에게 응답 --> <!-- → JSP 문서를 이용하여 응답하지 않고 메소드의 명령을 실행하여 응답 처리 --> <!-- → 반드시 JSP 문서로 응답되는 ViewResolver 객체로바 우선순위를 높도록 설정 --> <beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <beans:property name="order" value="1"/> </beans:bean><!-- BeanNameViewResolver 객체에 의해 실행될 클래스를 Spring Bean으로 등록 --> <!-- → 요청 처리 메소드의 반환값(ViewName)과 Spring Bean의 식별자(beanName)이 같아야 Front Controller에 의해 객체의 실행 메소드를 호출하여 응답 처리 --> <beans:bean class="xyz.itwill10.util.FileDownload" id="fileDownload"/>