54일차(1)/Spring(13) : 자료실 게시판 만들기, 파일 업로드 기능 구현
- File 게시판 dao, mapper 작성
- 파일 업로드 기능 구현
** mapper 에 별칭 부여하기
- parameterType="" resultType="" 작성되어 있는 내용이 너무 길다.
- 이 내용을 따로 작성해놓고 불러올 수 있다.
<!-- 자주 사용하는 type의 별칭을 등록해 놓고 Mapper xml 에서 사용할 수 있다. -->
<typeAliases>
<typeAlias type="com.sy.spring04.users.dto.UsersDto" alias="usersDto"/>
</typeAliases>
<mapper resource="mapper/FileMapper.xml"/>
- configuration에 별칭(alias)를 저장해 놓을 수 있다. (UsersDto)
- 그러면 mapper에서 이렇게 별칭으로 자동 완성되도록 뜬다.
- 홈에 자료실 목록 추가! (list)
FileDto
package com.sy.spring04.file.dto;
import org.springframework.web.multipart.MultipartFile;
public class FileDto {
private int num;
private String writer;
private String title;
//원본 파일명
private String orgFileName;
//파일 시스템에 저장된 파일명
private String saveFileName;
//파일의 크기
private long fileSize;
private String regdate;
//페이징 처리를 위한 필드
private int startRowNum;
private int endRowNum;
//파일 업로드 처리를 하기 위한 필드
private MultipartFile myFile;
public FileDto () {}
public FileDto(int num, String writer, String title, String orgFileName, String saveFileName, long fileSize,
String regdate, int startRowNum, int endRowNum, MultipartFile myFile) {
super();
this.num = num;
this.writer = writer;
this.title = title;
this.orgFileName = orgFileName;
this.saveFileName = saveFileName;
this.fileSize = fileSize;
this.regdate = regdate;
this.startRowNum = startRowNum;
this.endRowNum = endRowNum;
this.myFile = myFile;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getOrgFileName() {
return orgFileName;
}
public void setOrgFileName(String orgFileName) {
this.orgFileName = orgFileName;
}
public String getSaveFileName() {
return saveFileName;
}
public void setSaveFileName(String saveFileName) {
this.saveFileName = saveFileName;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
public int getStartRowNum() {
return startRowNum;
}
public void setStartRowNum(int startRowNum) {
this.startRowNum = startRowNum;
}
public int getEndRowNum() {
return endRowNum;
}
public void setEndRowNum(int endRowNum) {
this.endRowNum = endRowNum;
}
public MultipartFile getMyFile() {
return myFile;
}
public void setMyFile(MultipartFile myFile) {
this.myFile = myFile;
}
}
FileDao 인터페이스
package com.sy.spring04.file.dao;
import java.util.List;
import com.sy.spring04.file.dto.FileDto;
public interface FileDao {
public void insert(FileDto dto);
public FileDto getData(int num);
public void delete(int num);
public List<FileDto> getList(FileDto dto);
public int getCount(FileDto dto);
}
- getList 목록을 리턴하는 메소드
- getList에서 Dto를 넘겨주는 이유는 페이징처리를 위해서!(startRowNum, endRowNum)
그리고 검색기능을 만들 것인데, 검색 키워드도 Dto에 담아서 전달할 예정이다.
- getCount는 파일 전체의 개수를 세는 메소드(페이징처리를 위해)
그리고 검색키워드에 맞는 자료가 몇개가 있는지 찾는 작업에 dto를 인자로 받는게 필요하다.
FileService 인터페이스
package com.sy.spring04.file.service;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.sy.spring04.file.dto.FileDto;
public interface FileService {
//파일 목록 얻어오기
public void getList(HttpServletRequest request);
//업로드된 파일 저장하기
public void saveFile(FileDto dto, ModelAndView mView,
HttpServletRequest request);
//파일하나의 정보 얻어오기
public void getFileData(int num, ModelAndView mView);
//파일 삭제하기
public void deleteFile(int num, HttpServletRequest request);
}
FileMapper
<?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="file">
<insert id="insert" parameterType="fileDto">
INSERT INTO board_file
(num, writer, title, orgFileName, saveFileName, fileSize, regdate)
VALUES(board_file_seq.NEXTVAL, #{writer}, #{title},
#{orgFileName}, #{saveFileName}, #{fileSize}, SYSDATE)
</insert>
<select id="getData" parameterType="int" resultType="fileDto">
SELECT num,writer,title,orgFileName,saveFileName,fileSize,regdate
FROM board_file
WHERE num=#{num}
</select>
<delete id="delete" parameterType="int">
DELETE FROM board_file
WHERE num=#{num}
</delete>
<select id="getList" parameterType="fileDto"
resultType="fileDto">
SELECT *
FROM
(SELECT result1.*, ROWNUM AS rnum
FROM
(SELECT num,writer,title,orgFileName,saveFileName,
fileSize,regdate
FROM board_file
<choose>
<when test="title != null and orgFileName != null">
WHERE title LIKE '%'||#{title}||'%' OR orgFileName LIKE '%'||#{orgFileName}||'%'
</when>
<when test="title != null">
WHERE title LIKE '%'||#{title}||'%'
</when>
<when test="writer != null">
WHERE writer LIKE '%'||#{writer}||'%'
</when>
</choose>
ORDER BY num DESC) result1)
WHERE rnum BETWEEN #{startRowNum} AND #{endRowNum}
</select>
<select id="getCount" parameterType="fileDto" resultType="int">
SELECT NVL(MAX(ROWNUM), 0)
FROM board_file
<choose>
<when test="title != null and orgFileName != null">
WHERE title LIKE '%'||#{title}||'%' OR orgFileName LIKE '%'||#{orgFileName}||'%'
</when>
<when test="title != null">
WHERE title LIKE '%'||#{title}||'%'
</when>
<when test="writer != null">
WHERE writer LIKE '%'||#{writer}||'%'
</when>
</choose>
</select>
</mapper>
- FileMapper를 Configuration 목록에 등록해주기
- FileDaoImpl
package com.sy.spring04.file.dao;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.sy.spring04.file.dto.FileDto;
@Repository
public class FileDaoImpl implements FileDao{
@Autowired
private SqlSession session;
@Override
public void insert(FileDto dto) {
session.insert("file.insert", dto);
}
@Override
public FileDto getData(int num) {
return session.selectOne("file.getData", num);
}
@Override
public void delete(int num) {
session.delete("file.delete", num);
}
@Override
public List<FileDto> getList(FileDto dto) {
return session.selectList("file.getList", dto);
}
@Override
public int getCount(FileDto dto) {
return session.selectOne("file.getCount", dto);
}
}
mapper - insert
<insert id="insert" parameterType="fileDto">
INSERT INTO board_file
(num, writer, title, orgFileName, saveFileName, fileSize, regdate)
VALUES(board_file_seq.NEXTVAL, #{writer}, #{title},
#{orgFileName}, #{saveFileName}, #{fileSize}, SYSDATE)
</insert>
- Dao에서 insert 메소드를 작성하면서 mapper의 name, id, parameterType 생각하기
- 1번을 보고 2번을 작성할 수 있으면 된다.
- session을 사용해서 가져온 값을 바로 리턴해준다.
- resultType은 FileDto이다.
- selectOne의 경우, return Type이 곧 resultType이다.
- 여기서 parameter는 오직 하나. select된 row 하나의 정보를 dto에 담아달라는 의미
- 사용자가 one으로 사용하든 list로 사용하든 상관없다.
- SelectList로 작성하게 되면 이렇게 된다.
- 리턴타입 : List / resultType: FileDto(제너릭타입<>)
- 똑같은 sql문의 select문을 사용하더라도
selectList로 사용하느냐 selectOne으로 사용하느냐에 따라 리턴 타입이 달라진다. 기억하기!!
- getList
<select id="getList" parameterType="fileDto"
resultType="fileDto">
SELECT *
FROM
(SELECT result1.*, ROWNUM AS rnum
FROM
(SELECT num,writer,title,orgFileName,saveFileName,
fileSize,regdate
FROM board_file
ORDER BY num DESC) result1)
WHERE rnum BETWEEN #{startRowNum} AND #{endRowNum}
</select>
- getList. 페이징 처리에 필요한 sql문 (서브쿼리 사용)
1) 정렬
2) 행 번호 부여
3) 원하는 행 추려내기
- getCount의 경우 숫자를 하나 select하는 것이므로 resultType은 int이다.
- 나중에 검색기능을 만들고 검색 키워드가 있는 경우
sql의 저 위치에 WHERE절을 추가할 예정! (동적 sql문)
- configuration.xml에 mapper와 alias 등록
- 등록하고 나면 mapper에서 이렇게 일괄 수정할 수 있다.(ctrl+F)
FileServiceImpl
package com.sy.spring04.file.service;
import java.io.File;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.sy.spring04.file.dao.FileDao;
import com.sy.spring04.file.dto.FileDto;
@Service
public class FileServiceImpl implements FileService{
@Autowired
private FileDao dao;
@Override
public void getList(HttpServletRequest request) {
//한 페이지에 몇개씩 표시할 것인지
final int PAGE_ROW_COUNT=5;
//하단 페이지를 몇개씩 표시할 것인지
final int PAGE_DISPLAY_COUNT=5;
//보여줄 페이지의 번호를 일단 1이라고 초기값 지정
int pageNum=1;
//페이지 번호가 파라미터로 전달되는지 읽어와 본다.
String strPageNum=request.getParameter("pageNum");
//만일 페이지 번호가 파라미터로 넘어 온다면
if(strPageNum != null){
//숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
pageNum=Integer.parseInt(strPageNum);
}
//보여줄 페이지의 시작 ROWNUM
int startRowNum=1+(pageNum-1)*PAGE_ROW_COUNT;
//보여줄 페이지의 끝 ROWNUM
int endRowNum=pageNum*PAGE_ROW_COUNT;
//하단 시작 페이지 번호
int startPageNum = 1 + ((pageNum-1)/PAGE_DISPLAY_COUNT)*PAGE_DISPLAY_COUNT;
//하단 끝 페이지 번호
int endPageNum=startPageNum+PAGE_DISPLAY_COUNT-1;
//전체 글의 갯수
int totalRow=dao.getCount( new FileDto() );
//전체 페이지의 갯수 구하기
int totalPageCount=(int)Math.ceil(totalRow/(double)PAGE_ROW_COUNT);
//끝 페이지 번호가 이미 전체 페이지 갯수보다 크게 계산되었다면 잘못된 값이다.
if(endPageNum > totalPageCount){
endPageNum=totalPageCount; //보정해 준다.
}
//FileDto 객체를 생성해서
FileDto dto=new FileDto();
//위에서 계산된 startRowNum , endRowNum 을 담아서
dto.setStartRowNum(startRowNum);
dto.setEndRowNum(endRowNum);
//파일 목록을 select 해 온다.
List<FileDto> list=dao.getList(dto);
//view page 에 전달하기 위해 request scope에 담는다.
request.setAttribute("list", list);
request.setAttribute("pageNum", pageNum);
request.setAttribute("startPageNum", startPageNum);
request.setAttribute("endPageNum", endPageNum);
request.setAttribute("totalPageCount", totalPageCount);
}
@Override
public void saveFile(FileDto dto, ModelAndView mView, HttpServletRequest request) {
//업로드된 파일의 정보를 가지고 있는 MultipartFile 객체의 참조값 얻어오기
MultipartFile myFile=dto.getMyFile();
//원본 파일명
String orgFileName=myFile.getOriginalFilename();
//파일의 크기
long fileSize=myFile.getSize();
// webapp/resources/upload 폴더 까지의 실제 경로(서버의 파일시스템 상에서의 경로)
String realPath=request.getServletContext().getRealPath("/resources/upload");
//저장할 파일의 상세 경로
String filePath=realPath+File.separator;
//디렉토리를 만들 파일 객체 생성
File upload=new File(filePath);
if(!upload.exists()) {//만일 디렉토리가 존재하지 않으면
upload.mkdir(); //만들어 준다.
}
//저장할 파일 명을 구성한다.
String saveFileName=
System.currentTimeMillis()+orgFileName;
try {
//upload 폴더에 파일을 저장한다.
myFile.transferTo(new File(filePath+saveFileName));
System.out.println(filePath+saveFileName);
}catch(Exception e) {
e.printStackTrace();
}
//dto 에 업로드된 파일의 정보를 담는다.
String id=(String)request.getSession().getAttribute("id");
dto.setWriter(id); //세션에서 읽어낸 파일 업로더의 아이디
dto.setOrgFileName(orgFileName);
dto.setSaveFileName(saveFileName);
dto.setFileSize(fileSize);
//fileDao 를 이용해서 DB 에 저장하기
dao.insert(dto);
//view 페이지에서 사용할 모델 담기
mView.addObject("dto", dto);
}
@Override
public void getFileData(int num, ModelAndView mView) {
//다운로드할 파일의 정보를 얻어와서
FileDto dto=dao.getData(num);
//ModelAndView 객체에 담아준다
mView.addObject("dto", dto);
}
@Override
public void deleteFile(int num, HttpServletRequest request) {
//삭제할 파일의 정보를 얻어오기
FileDto dto=dao.getData(num);
//파일 시스템에서 삭제(upload 폴더에서 해당파일을 삭제)
String saveFileName=dto.getSaveFileName();
String path=request.getServletContext().getRealPath("/resources/upload")
+File.separator+saveFileName;
//경로를 사용해서 파일객체를 생성해서
new File(path).delete();
//DB에서 파일 정보 삭제
dao.delete(num);
}
}
- 인터페이스를 구현해주고,
@Service 추가, FileDao @Autowired 해주기
FileController
package com.sy.spring04.file.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.sy.spring04.file.dto.FileDto;
import com.sy.spring04.file.service.FileService;
@Controller
public class FileController {
@Autowired
private FileService service;
@RequestMapping("/file/list")
public String list(HttpServletRequest request) {
service.getList(request);
return "file/list";
}
@RequestMapping("/file/upload_form")
public String uploadForm() {
return "file/upload_form";
}
@RequestMapping("/file/upload")
public ModelAndView upload(FileDto dto, ModelAndView mView, HttpServletRequest request) {
service.saveFile(dto, mView, request);
mView.setViewName("file/upload");
return mView;
}
}
- @Controller 추가 / service @Autowired
- getList 는 request를 전달해주는 구조. 전달해주면 파일목록을 setAttribute로 받아주면된다.
- 왜 request를 넘겨주었냐면.. 예전 코드를 최대한 재활용하기 위해서 ㅎㅎ
- 이전에 List 에서 작성했던 java 코드는 이제 서비스 메소드 안에서 처리할 수 있도록 작성해야한다.
- 이 정보를 처리하기 위해서는 request 객체가 필요하다.
- dao는 그냥 존재하므로 getInstance()를 사용할 필요가 없다. 그냥 dao로 수정해주면 된다.
List<FileDto> list=dao.getList(dto);
request.setAttribute("list", list); ← 이 코드가 추가되었다.
- 이전 jsp에서는 startRowNum, endRowNum 를 select해와서 바로 사용했는데
spring MVC 에서는 일단 request영역에 담고 view페이지로 포워드이동해서 응답한다.
- list안에는 FileDto 타입이 담겨있다. 필드명을 사용해 가져오기!
- c:forEach문으로 수정해주기
- 삭제 버튼은 작성자와 로그인한 사람이 같을때만 조건부로 출력해준다.
- pageNum, StartPageNum, endPageNum, TotalpageCount
4개의 정보는 req 영역에 담아서 가져와야한다. (service에서 추가해줌)
- 서비스에서 request 에 넣어서 담아준다.
- dto에 한번에 담아서 전달할 수도 있다.
- startPageNum이 1이 아니면 prev 링크가 나오도록 c:if로 작성
- 변수명을 i 로 지정해서 1씩 증가하는 forEach문 작성
- JSTL과 EL로 수정해주기
- interceptor 추가
<interceptors>
<interceptor>
<mapping path="/users/*"/>
<mapping path="/file/*"/>
<exclude-mapping path="/users/signup_form"/>
<exclude-mapping path="/users/signup"/>
<exclude-mapping path="/users/loginform"/>
<exclude-mapping path="/users/login"/>
<exclude-mapping path="/file/list"/>
<beans:ref bean="loginInterceptor"/>
</interceptor>
</interceptors>
- file 하위 페이지 추가하기
- interceptor bean 하나를 여러 interceptor 에 적용가능!
upload_form.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/file/upload_form.jsp</title>
</head>
<body>
<div class="container">
<h3>파일 업로드 폼 입니다.</h3>
<!--
파일 업로드 폼 작성법
1. method="post"
2. enctype="multipart/form-data"
3. <input type="file" />
- enctype="multipart/form-data" 가 설정된 폼을 전송하면
폼전송된 내용을 추출할때 HttpServletRequest 객체로 추출을 할수 없다
MultipartRequest 객체를 이용해서 추출해야 한다.
-->
<form action="upload" method="post" enctype="multipart/form-data">
<div>
<label for="title">제목</label>
<input type="text" name="title" id="title"/>
</div>
<div>
<label for="myFile">첨부파일</label>
<input type="file" name="myFile" id="myFile"/>
</div>
<button type="submit">업로드</button>
</form>
</div>
</body>
</html>
- 제목, 첨부파일만 넣도록 되어있음
- input요소에 지정한 name값 기억! fileDto로 받을 수 있다.
- 단 받으려면 FileDto에 title, myFile이라는 필드가 있어야한다.
- Dto에 필드가 준비되어 있다.
- file의 타입이 반드시 MultipartFile이어야 한다!!
- 이곳에 업로드하면 DTO 에 파일 이름(title)과 업로드된 파일의 정보(myFile)가 담긴다.
- dto에 담아서 이 메소드에서 사용한다.
- 서비스 메소드에 request가 필요하므로 메소드의 인자로 추가해주기
- Service의 saveFile 메소드 작성
@Override
public void saveFile(FileDto dto, ModelAndView mView, HttpServletRequest request) {
//업로드된 파일의 정보를 가지고 있는 MultipartFile 객체의 참조값 얻어오기
MultipartFile myFile=dto.getMyFile();
//원본 파일명
String orgFileName=myFile.getOriginalFilename();
//파일의 크기
long fileSize=myFile.getSize();
// webapp/resources/upload 폴더 까지의 실제 경로(서버의 파일시스템 상에서의 경로)
String realPath=request.getServletContext().getRealPath("/resources/upload");
//저장할 파일의 상세 경로
String filePath=realPath+File.separator;
//디렉토리를 만들 파일 객체 생성
File upload=new File(filePath);
if(!upload.exists()) {//만일 디렉토리가 존재하지 않으면
upload.mkdir(); //만들어 준다.
}
//저장할 파일 명을 구성한다.
String saveFileName=
System.currentTimeMillis()+orgFileName;
try {
//upload 폴더에 파일을 저장한다.
myFile.transferTo(new File(filePath+saveFileName));
System.out.println(filePath+saveFileName);
}catch(Exception e) {
e.printStackTrace();
}
//dto 에 업로드된 파일의 정보를 담는다.
String id=(String)request.getSession().getAttribute("id");
dto.setWriter(id); //세션에서 읽어낸 파일 업로더의 아이디
dto.setOrgFileName(orgFileName);
dto.setSaveFileName(saveFileName);
dto.setFileSize(fileSize);
//fileDao 를 이용해서 DB 에 저장하기
dao.insert(dto);
//view 페이지에서 사용할 모델 담기
mView.addObject("dto", dto);
}
- 정확한 경로에 어떤 파일명으로 저장할지 정해야 한다.
- 파일 객체를 생성해서 원하는 파일명을 구성하는 것이 제일 중요!
- 업로드 폴더까지의 절대경로는 메소드로 얻어낼 수 있다.(getRealPath)
- 나머지 파일구분자(seperator)는 파일 객체의 필드로 얻어낼 수 있다.
- 이것들을 사용해서 파일객체를 저장할 정보를 만들어낸다 → 만들어진 경로를 transferTo에 담아줌
upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/file/upload.jsp</title>
</head>
<body>
<div class="container">
<h3>Alert</h3>
<p>
<strong>${dto.orgFileName }</strong> 파일이 업로드 되었습니다.
<a href="${pageContext.request.contextPath}/file/list">확인</a>
</p>
</div>
</body>
</html>
- 파일을 업로드하면 콘솔창에 이렇게 파일이 업로드된 경로가 나타난다.
- 시스템상의 어떤 경로에 저장되는지를 알아내는 메소드가 getRealPath() 이다.
이 메소드를 사용해서만 얻어낼 수 있다.
- 업로드폴더 뒤에 있는 파일구분자( \ )는 File.seperator 를 사용해서 얻어낸 것이다.
- \ 으로 직접 작성해서 넣어버리면 리눅스에서는 쓸 수 없다.
- File.separator 라고 입력하면 시스템에서 알아서 윈도우에서는 \ 를, 리눅스에서는 / 를 반환해준다.
- currentTimeMillis() 가 임의의 숫자를 넣어주었고, 뒤에 orgFileName 을 붙여서 파일명이 완성되었다.
- 각각의 코드가 무엇을 가리키는지 알고, 필요에 따라 수정해서 사용할 수 있으면 된다.
- new File( ) 괄호안에 에 이 경로 문자열을 넘겨준다
- 최종적으로는 이 파일을 transferTo() 에 넘겨줄 수 있으면 된다.
'국비교육(22-23)' 카테고리의 다른 글
55일차(1)/Spring(15) : Cafe 게시판 기능 구현 (목록보기, 새글 작성) (1) | 2022.12.25 |
---|---|
54일차(2)/Spring(14) : 자료실 파일 다운로드, 삭제, 검색 기능 구현 (0) | 2022.12.22 |
53일차(1)/Spring(12) : ajax 요청 JSON으로 응답하기, 파일 업로드 처리 (0) | 2022.12.21 |
52일차(2)/Spring(11) : Interceptor 추가, 비밀번호 암호화 및 수정, 회원 삭제 기능 구현 (1) | 2022.12.20 |
52일차(1)/Spring(10) : 회원가입, 로그인, 로그아웃, 회원정보 보기 기능 구현 (1) | 2022.12.20 |