✿∘˚˳°∘°
77일차 : SpringMVC - 게시판2 본문
20230317
어제 작성했던 파일네이밍을 클래스의 메소드로 따로빼서 사용할것 (다른데에서도 똑같이 사용하기 때문에)
파일 업로드시 board notice 둘다 파일업로드를 한다고 가정하면 달라지는 부분은 파일이 올라가는 경로+실제파일
-> 공통적인 부분을 따로 만들어서 매개변수를 받아 달라지는 부분만 수정(모듈화)
FileManager.java
@Component //객체생성을 위한것 - servlet-context.xml의 component-scan과 함께 이루어져야한다.
public class FileManager {
//파일업로드를 위한 메소드
public String upload(String savePath, MultipartFile file) {
String filename = file.getOriginalFilename();
String onlyFilename = filename.substring(0, filename.lastIndexOf("."));
String extention = filename.substring(filename.lastIndexOf("."));
String filepath = null;
int count = 0;
while(true) {
if(count == 0) {
filepath = onlyFilename+extention;
}else {
filepath = onlyFilename+"_"+count+extention;
}
File checkFile = new File(savePath+filepath);
if(!checkFile.exists()) {
break;
}
count++;
}
try {
FileOutputStream fos = new FileOutputStream(savePath+filepath);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bytes = file.getBytes();
bos.write(bytes);
bos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//filepath를 return을 String으로 잡아준것
return filepath;
}
}
- servlet-context.xml에서 component-scan을 잊지말자
<context:component-scan base-package="common" />
1. 수정된 글작성하기(INSERT)
FileManager를 사용하기위해 상단에 선언
@Autowired
private FileManager fileManager;
Controller
@RequestMapping(value="/boardWrite.do")
//파일업로드를 진행하기위해 request를 불러옴
public String boardWrite(Board b, MultipartFile[] boardFile, HttpServletRequest request) {
//파일목록을 저장할 list생성
ArrayList<FileVO> fileList = new ArrayList<FileVO>();
//제목,내용,작성자는 매개변수 b에 모두 들어있음
//파일이름은 b에 들어있지X 매개변수로 받음 -> 여러개를 받을거기때문에 자료형은 배열형태로
//첨부파일은 input type="file"의 name으로 매개변수를 생성 -> 첨부파일 갯수만큼 배열길이 생성
//첨부파일을 첨부하지 않아도 배열은 길이가 1
//System.out.println(b);
//System.out.println(boardFile.length);
//첨부파일 존재유무를 구분하는 법 - 배열의 첫번째가 비어있는지 아닌지 확인
//if(!boardFile[0].isEmpty()) {} if문은 이렇게 작성해주는게 좋다.
if(boardFile[0].isEmpty()) {
//첨부파일이 없는경우 진행할 로직이 없음
}else {
//첨부파일이 있는경우 파일업로드작업 수행
//1. 파일업로드 경로 설정
//getRealPath까지가 webapp
String savePath = request.getSession().getServletContext().getRealPath("/resources/upload/board/");
//2. 배열이므로 반복문을 이용해서 파일업로드 처리
for(MultipartFile file : boardFile) {
//파일명이 기존업로드한 파일명과 중복되면 기존파일을 삭제하고 새파일로 덮어쓰기됨 -> 파일명이 중복되지않게 처리해야 함
//(cos라이브러리에서는 new DefaultFileRenamePolicy가 파일이름이 중복일 경우 새로운이름을 붙여줬음 name1 name2)
String filename = file.getOriginalFilename(); //사용자가 업로드한 이름
//이걸 그대로 업로드하면 파일명이 중복되엇을 때 문제가 발생
//가정 : filename => test.txt인데 중복인 경우 => test_1.txt 확장자기준 앞을 바꿈
String filepath = fileManager.upload(savePath, file);
/*
//우선 text .txt를 분리함
String onlyFilename = filename.substring(0, filename.lastIndexOf("."));//test 0부터 뒤에서부터 .의 위치의 숫자를세서 그만큼까지
String extention = filename.substring(filename.lastIndexOf(".")); //.txt 매개변수를 하나만주면 그 매개변수부터 끝까지라는 의미
//실제 업로드할 파일명
String filepath = null;
//파일명 중복 시 뒤에 붙일 숫자
int count = 0; //횟수가 정해져있지 않음 > while문을 무한으로 돌리고 중복이아닐때 빠져나옴
while(true) {
if(count == 0) {
//첫번째 검증인 경우 숫자를 붙이지X
filepath = onlyFilename+extention; //test.txt
}else {
filepath = onlyFilename+"_"+count+extention; //test_1.txt
}
File checkFile = new File(savePath+filepath);
if(!checkFile.exists()) {
//checkFile이 존재하는지 물어보는 것 -> 중복이 아닐 때 break
break;
}
count++;
}
//파일명 중복체크 끝 -> 업로드할 파일명 확정 -> 파일업로드진행
//2-2. 중복처리가 끝난 파일 업로드
try {
//파일업로드를 위한 주스트림생성
FileOutputStream fos = new FileOutputStream(savePath+filepath);
//성능향상을 위한 보조스트림 생성
BufferedOutputStream bos = new BufferedOutputStream(fos);
//파일업로드
byte[] bytes = file.getBytes();
bos.write(bytes);
bos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} */
//파일업로드 끝(1개)
//DB에 저장하기 위해 FileVO형태의 객체를 생성해서 fileList추가
FileVO fileVO = new FileVO();
fileVO.setFilename(filename);
fileVO.setFilepath(filepath);
fileList.add(fileVO);
}//for문 끝
}
//비즈니스로직(board와 file_tbl에 insert진행)
int result = service.insertBoard(b, fileList);
if(result == (fileList.size()+1)) {
//insert가 총 첨부파일+1개만큼 일어남 -> board인서트(1번) + 첨부파일인서트(여러번)
//result를 누계했으므로
return "redirect:/boardList.do"; //컨트롤러를 거쳐가지않으면 리스트내용이 보이지않음
}else {
return "redirect:/";
}
}
- 주석 제외 Controller
@RequestMapping(value="/boardWrite.do")
public String boardWrite(Board b, MultipartFile[] boardFile, HttpServletRequest request) {
ArrayList<FileVO> fileList = new ArrayList<FileVO>();
if(boardFile[0].isEmpty()) {
}else {
String savePath = request.getSession().getServletContext().getRealPath("/resources/upload/board/");
for(MultipartFile file : boardFile) {
String filename = file.getOriginalFilename(); //사용자가 업로드한 이름
String filepath = fileManager.upload(savePath, file);
FileVO fileVO = new FileVO();
fileVO.setFilename(filename);
fileVO.setFilepath(filepath);
fileList.add(fileVO);
}
}
int result = service.insertBoard(b, fileList);
if(result == (fileList.size()+1)) {
return "redirect:/boardList.do";
}else {
return "redirect:/";
}
}
Service + Dao는 변화없음
2. 게시글 전체 목록(BoardList)
Controller
@RequestMapping(value="/boardList.do")
public String boardList(Model model) {
ArrayList<Board> list = service.selectBoardList();
model.addAttribute("list", list);
return "board/boardList";
}
Service
public ArrayList<Board> selectBoardList() {
ArrayList<Board> list = dao.selectBoardList();
return list;
}
Dao
public ArrayList<Board> selectBoardList() {
String query = "select board_no, board_title, board_writer, board_date from board order by 1 desc";
List list = jdbc.query(query, new BoardListRowMapper());
return (ArrayList<Board>)list;
}
List를 확인할때만 사용할 RowMapper - BoardListRowMapper.java( boardContent 가 없는걸 확인)
-> RowMapper는 필요한 만큼 생성하면 된다.
package kr.or.board.model.vo;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class BoardListRowMapper implements RowMapper{
@Override
public Object mapRow(ResultSet rset, int rowNum) throws SQLException {
Board b = new Board();
b.setBoardNo(rset.getInt("board_no"));
b.setBoardTitle(rset.getString("board_title"));
b.setBoardWriter(rset.getString("board_writer"));
b.setBoardDate(rset.getString("board_date"));
return b;
}
}
화면구현 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>Insert title here</title>
</head>
<body>
<h1>게시물 목록</h1>
<hr>
<c:if test="${not empty sessionScope.m}">
<h3><a href="/boardWriteFrm.do">게시글 작성하기</a></h3>
</c:if>
<table border="1">
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
<c:forEach items="${list }" var="b">
<tr>
<td>${b.boardNo }</td>
<td><a href="/boardView.do?boardNo=${b.boardNo}">${b.boardTitle }</a></td>
<td>${b.boardWriter }</td>
<td>${b.boardDate }</td>
</tr>
</c:forEach>
</table>
<a href="/">메인으로</a>
</body>
</html>
3. 게시글 상세보기(BoardView)
Controller
@RequestMapping(value="/boardView.do")
public String boardView(int boardNo, Model model) {
Board b = service.selectOneBoard(boardNo);
if(b != null) {
model.addAttribute("b", b);
return "board/boardView";
}else {
return "redirect:/boardList.do";
}
}
Service
public Board selectOneBoard(int boardNo) {
//보드 조회
Board b = dao.selectOneBoard(boardNo);
//파일 조회
if(b != null) {
ArrayList<FileVO> fileList = dao.selectFileList(boardNo);
b.setFileList(fileList);
}
return b;
}
Dao 1 : Board 테이블 조회
public Board selectOneBoard(int boardNo) {
String query = "select * from board where board_no = ?";
Object[] params = {boardNo};
//이번엔 content가 있기 때문에 BoardListRowMapper는 사용할 수 없음(boardContent가 없음)
List list = jdbc.query(query, params, new BoardRowMapper());
if(list.isEmpty()) {
return null;
}else {
return (Board)list.get(0);
}
}
Dao 2 : File_tbl 테이블 조회
public ArrayList<FileVO> selectFileList(int boardNo) {
String query = "select * from file_tbl where board_no = ?";
Object[] params = {boardNo};
List list = jdbc.query(query, params, new FileRowMapper());
return (ArrayList<FileVO>)list;
}
파일도 전부 조회해서 Board VO에 있는 fileList에 set으로 넣어준다.
화면구현 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>Insert title here</title>
</head>
<body>
<h1>게시물 상세보기</h1>
<hr>
<table border="1">
<tr>
<th>글번호</th>
<td>${b.boardNo }</td>
<th>제목</th>
<td>${b.boardTitle }</td>
<th>작성자</th>
<td>${b.boardWriter }</td>
<th>작성일</th>
<td>${b.boardDate }</td>
</tr>
<tr>
<th>첨부파일</th>
<td colspan="7">
<%--첨부파일은 여러개일 수 있으므로 for문을 돌림 --%>
<c:forEach items="${b.fileList }" var="f">
<a href="/boardFileDown.do?fileNo=${f.fileNo }">${f.filename }</a>
</c:forEach>
</td>
</tr>
<tr>
<th>내용</th>
<td colspan="7">${b.boardContent }</td>
</tr>
</table>
<c:if test="${not empty sessionScope.m && sessionScope.m.memberId eq b.boardWriter }">
<a href="/boardUpdateFrm.do?boardNo=${b.boardNo }">수정</a>
<a href="/boardDelete.do?boardNo=${b.boardNo }">삭제</a>
</c:if>
</body>
</html>
4. 파일다운로드
Controller
@RequestMapping(value="/boardFileDown.do")
public void boardFileDown(int fileNo, HttpServletRequest request, HttpServletResponse response) {
//fileNo : DB에서 filename, filepath를 조회해오기위한 용도
//request : 파일위치 찾을 때 사용
//response : 파일다운로드 로직 구현 시 사용
//리턴을 하지않음 - 페이지이동이 필요없으므로
//filename과 filepath를 찾아오기위해서
FileVO file = service.getFile(fileNo);
//파일경로
String root = request.getSession().getServletContext().getRealPath("/resources/upload/board/");
String downFile = root+file.getFilepath();
//파일을 읽어오기위한 주스트림생성(속도개선을위한 보조스트림생성)
try {
FileInputStream fis = new FileInputStream(downFile);
BufferedInputStream bis = new BufferedInputStream(fis);
//읽어온 파일을 사용자에게 내보낼 스트림생성
ServletOutputStream sos = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(sos);
//파일명 처리
String resFilename = new String(file.getFilename().getBytes("UTF-8"), "ISO-8859-1");
response.setContentType("application/octet-stream");//파일형식이란것을 알려줌
response.setHeader("Content-Disposition", "attachment;filename="+resFilename);//파일이름을 알려줌
//파일전송
while(true) {
int read = bis.read();
//파일을 계속 읽다가 다읽으면 종료
if(read != -1) {
bos.write(read);
}else {
break;
}
}
bos.close();
bis.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Service(파일path와 name을 찾기위한 로직)
public FileVO getFile(int fileNo) {
return dao.getFile(fileNo);
}
Dao
public FileVO getFile(int fileNo) {
String query = "select * from file_tbl where file_no = ?";
Object[] params = {fileNo};
List list = jdbc.query(query, params, new FileRowMapper());
return (FileVO)list.get(0);
}
5. 게시글 삭제( deleteBoard )
Controller
@RequestMapping(value="/boardDelete.do")
public String boardDelete(int boardNo, HttpServletRequest request) {
//int result = service.deleteBoard(boardNo);
//원래는 int로 받는게 맞지만 file또한 지워야 하기 때문에 file목록을 가져올것
//DB를 삭제하고, 서버에 업로드 되어있는 파일을 지우기 위해서 파일목록을 가져옴
ArrayList<FileVO> list = service.deleteBoard(boardNo);
//list가 null이면 실패, 아니면 성공
if(list == null) {
return "redirect:/boardView.do?boardNo="+boardNo;
}else {
//성공 시 실제 file을 삭제
String savePath = request.getSession().getServletContext().getRealPath("/resources/upload/board/");
for(FileVO file : list) {
boolean deleteResult = fileManager.deleteFile(savePath, file.getFilepath());
if(deleteResult) {
System.out.println("파일삭제성공");
}else {
System.out.println("파일삭제실패");
}
}
return "redirect:/boardList.do";
}
}
Service - 파일또한 삭제해줘야 하기 때문에 파일먼저 조회 후 게시글 삭제진행
public ArrayList<FileVO> deleteBoard(int boardNo) {
//삭제이후에 조회를 하면 on delete cascade 때문에 이미 지워진 상태이기 때문에 확인불가
ArrayList<FileVO> fileList = dao.selectFileList(boardNo);
int result = dao.deleteBoard(boardNo);
if(result > 0) {
return fileList;
}else {
return null;
}
}
Dao 1 : 파일목록 조회
public ArrayList<FileVO> selectFileList(int boardNo) {
String query = "select * from file_tbl where board_no = ?";
Object[] params = {boardNo};
List list = jdbc.query(query, params, new FileRowMapper());
return (ArrayList<FileVO>)list;
}
Dao 2 : 게시글 삭제
public int deleteBoard(int boardNo) {
String query = "delete from board where board_no = ?";
Object[] params = {boardNo};
int result = jdbc.update(query, params);
return result;
}
6. 게시글 수정(BoardUpdate)
- 기존 게시글 정보를 가지고 수정폼으로 이동
@RequestMapping(value="/boardUpdateFrm.do")
public String boardUpdateFrm(int boardNo, Model model) {
Board b = service.selectOneBoard(boardNo);
model.addAttribute("b", b);
return "board/BoardUpdateFrm";
}
수정폼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>Insert title here</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1>게시글 수정</h1>
<hr>
<form action="/boardUpdate.do" method="post" enctype="multipart/form-data" id="updateFrm">
<table border="1">
<tr>
<th>글번호</th>
<td>
<input type="hidden" name="boardNo" value="${b.boardNo }">
${b.boardNo }
</td>
</tr>
<tr>
<th>제목</th>
<td>
<input type="text" name="boardTitle" value="${b.boardTitle }">
</td>
</tr>
<tr>
<th>작성자</th>
<td>${b.boardWriter }</td>
</tr>
<tr>
<th>작성일</th>
<td>${b.boardDate }</td>
</tr>
<tr>
<th>첨부파일</th>
<td>
<c:forEach items="${b.fileList }" var="f">
<p>
${f.filename }
<button type="button" onclick="deleteFile(this, ${f.fileNo}, '${f.filepath }');">삭제</button>
</p>
</c:forEach>
</td>
</tr>
<tr>
<th>첨부파일 추가</th>
<td>
<input type="file" name="boardFile" multiple>
</td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="boardContent">${b.boardContent }</textarea>
</td>
</tr>
<tr>
<th colspan="2"><input type="submit" value="수정하기"></th>
</tr>
</table>
</form>
<script>
function deleteFile(obj, fileNo, filepath){
//화면에서 파일을 안보여주게 하기위해 obj(this)를 가져옴 -> p태그를 지워줄것
//file인풋을 만들어서 form에 넣어줄것 -> DB삭제를 위한 fileNo / 파일삭제를 위한 filepath
//<input>
const fileNoInput = $("<input>");
//<input name="fileNo">
fileNoInput.attr("name", "fileNo");
//<input name="fileNo" value="글번호숫자">
fileNoInput.val(fileNo);
//<input name="fileNo" value="글번호숫자" style="display:none;">
fileNoInput.hide();
const filepathInput = $("<input>");
filepathInput.attr("name", "filepath");
filepathInput.val(filepath);
filepathInput.hide();
$("#updateFrm").append(fileNoInput).append(filepathInput);
$(obj).parent().remove();
}
</script>
</body>
</html>
수정 Controller
@RequestMapping(value="/boardUpdate.do")
public String boardUpdate(Board b, int[] fileNo, String[] filepath, MultipartFile[] boardFile, HttpServletRequest request) {
//fileVO file로 받으면 문제점 발생 -> 하나만 지우면 fileVO로 받으면 되지만 여러개이기 때문에 각각 배열을 받아야함
//b -> boardNo, boardTitle, boardContent
//fileNo : 삭제할 파일번호(DB) / filepath : 삭제할 파일이름(물리적으로 파일폴더에서삭제)
//boardFile : 새로올릴 파일 / request : 새파일 경로잡기위함
//file -> fileNo, filepath
//첨부파일이 추가로 들어왔으면 INSERT
ArrayList<FileVO> fileList = new ArrayList<FileVO>();
String savePath = request.getSession().getServletContext().getRealPath("/resources/upload/board/");
if(!boardFile[0].isEmpty()) {
for(MultipartFile file : boardFile ) {
String filename = file.getOriginalFilename();
String upFilepath = fileManager.upload(savePath, file);
FileVO fileVO = new FileVO();
fileVO.setFilename(filename);
fileVO.setFilepath(upFilepath);
fileList.add(fileVO);
//파일업로드 작업 완료
}
}
int result = service.boardUpdate(b, fileList, fileNo);
//업데이트 성공 조건 : result == 삭제한 파일수 + 추가한파일수 + 1
if(fileNo != null && (result == (fileList.size() + fileNo.length + 1))) {
//삭제한 파일이 있는 상태에서 모든 로직을 성공 했을 때
//실제파일 지우기
for(String delFile : filepath) {
boolean delResult = fileManager.deleteFile(savePath, delFile);
if(delResult) {
System.out.println("삭제성공");
}else {
System.out.println("삭제실패");
}
}
return "redirect:/boardView.do?boardNo="+b.getBoardNo();
}else if(fileNo == null && (result == fileList.size()+1)) {
//삭제한 파일이 없는 상태에서 모든 로직을 성공 했을 때
return "redirect:/boardView.do?boardNo="+b.getBoardNo();
}else {
//실패
return "redirect:/boardList.do";
}
}
Service
public int boardUpdate(Board b, ArrayList<FileVO> fileList, int[] fileNo) {
//1. board테이블 수정
int result = dao.updateBoard(b);
if(result > 0) {
//2. 기존 첨부파일 삭제
//기존첨부파일을 삭제하지 않을 경우 -> 추가한 input이 없음 -> int[] fileNo 가 null
if(fileNo != null) {
//삭제할 첨부파일이 있는경우
for(int no : fileNo) {
result += dao.deleteFile(no);
}
}
//3. 새로운 첨부파일이 있으면 추가
for(FileVO f : fileList) {
f.setBoardNo(b.getBoardNo());
result += dao.insertFile(f);
}
}
return result;
}
Dao 1 : 게시글 업데이트
public int updateBoard(Board b) {
String query = "update board set board_title=?, board_content=? where board_no =?";
Object[] params = {b.getBoardTitle(), b.getBoardContent(), b.getBoardNo()};
int result = jdbc.update(query, params);
return result;
}
Dao 2 : 삭제할 파일이 있으면 파일 삭제
public int deleteFile(int no) {
String query = "delete from file_tbl where file_no = ?";
Object[] params = {no};
int result = jdbc.update(query, params);
return result;
}
Dao 3 : 새로추가된 파일이 있으면 INSERT(기존 파일INSERT 이용)
public int insertFile(FileVO file) {
String query = "insert into file_tbl values(file_seq.nextval, ?, ?, ?)";
Object[] params = {file.getBoardNo(), file.getFilename(), file.getFilepath()};
int result = jdbc.update(query, params);
return result;
}
'국비수업 > Spring' 카테고리의 다른 글
79일차 : Dynamic Mybatis (0) | 2023.03.21 |
---|---|
78일차 : Mybatis (0) | 2023.03.21 |
76일차 : 정규화, SpringMVC - 게시판 (0) | 2023.03.16 |
75일차 : SpringMVC - 회원, 공지사항 (0) | 2023.03.15 |
74일차 : loC, DI, springMVC (0) | 2023.03.14 |