일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- oracle
- 대덕인재개발원
- Java
- 어윈 사용법
- 다형성
- 참조형변수
- NestedFor
- 예외미루기
- 사용자예외클래스생성
- 자바
- 예외처리
- 컬렉션 타입
- exception
- 객체 비교
- 생성자오버로드
- 인터페이스
- 집합_SET
- 자동차수리시스템
- GRANT VIEW
- 한국건설관리시스템
- abstract
- EnhancedFor
- 메소드오버로딩
- cursor문
- 추상메서드
- 제네릭
- 정수형타입
- 환경설정
- 컬렉션프레임워크
- 오라클
- Today
- Total
거니의 velog
(14) 스프링으로 답변형 게시판 만들기 3 본문
6. 글상세창 구현하기
* 이번에는 글상세창으로 표시하는 기능을 구현해 보자.
1. 매퍼 파일 board.xml에 전달된 글 번호에 대해 글 정보를 조회하는 SQL문을 추가한다.
<select id="selectArticle" resultType="articleVO" parameterType="int">
<![CDATA[
SELECT * from t_board
where articleNO = #{articleNO}
]]>
</select>
2. 글상세창(viewArticle.jsp)을 나타낼 타일즈 기능을 설정한다.
[tiles_board.xml]
<definition name="/board/viewArticle" extends="baseLayout">
<put-attribute name="title" value="글상세창" />
<put-attribute name="body" value="/WEB-INF/views/board/viewArticle.jsp" />
</definition>
3. 첨부 파일을 표시할 파일 다운로드 컨트롤러인 FileDownloadController를 common/file 패키지에 구현한다.
4. 이전의 FileDownloadController 클래스를 복사해 붙여 넣은 후 다음과 같이 수정한다.
package com.myspring.pro30.common.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class FileDownloadController {
private static final String ARTICLE_IMAGE_REPO = "C:\\board\\article_image";
@RequestMapping("/download.do")
protected void download(@RequestParam("imageFileName") String imageFileName, // 이미지 파일 이름을 바로 설정한다.
@RequestParam("articleNO") String articleNO,
HttpServletResponse response) throws Exception {
OutputStream out = response.getOutputStream();
String downFile = ARTICLE_IMAGE_REPO + "\\" + articleNO + "\\" + imageFileName; // 글 번호와 파일 이름으로 다운로드 할 파일 경로를 설정한다.
File file = new File(downFile);
response.setHeader("Cache-Control", "no-cache");
response.addHeader("Content-disposition", "attachment; fileName=" + imageFileName);
FileInputStream in = new FileInputStream(file);
byte[] buffer = new byte[1024 * 8];
while (true) {
int count = in.read(buffer);
if (count == -1)
break;
out.write(buffer, 0, count);
}
in.close();
out.close();
}
}
5. 글목록창에서 전달된 글 번호를 이용하여 해당 글 정보를 조회한다.
[BoardControllerImpl.java]
@RequestMapping(value = "/board/viewArticle.do", method = RequestMethod.GET)
public ModelAndView viewArticle(@RequestParam("articleNO") int articleNO, // 조회할 글 번호를 가져온다.
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String viewName = (String) request.getAttribute("viewName");
articleVO = boardService.viewArticle(articleNO); // 조회한 글 정보를 articleVO에 설정한다.
ModelAndView mav = new ModelAndView();
mav.setViewName(viewName);
mav.addObject("article", articleVO);
return mav;
}
6. Service 클래스와 DAO 클래스의 메서드는 다음과 같다.
@Override
public ArticleVO viewArticle(int articleNO) throws Exception {
ArticleVO articleVO = boardDAO.selectArticle(articleNO);
return articleVO;
}
@Override
public ArticleVO selectArticle(int articleNO) throws DataAccessException {
return sqlSession.selectOne("mapper.board.selectArticle", articleNO);
}
7. 마지막으로 글 상세 정보를 표시할 JSP 파일을 다음과 같이 작성한다. 단, 글 수정과 삭제는 자신이 작성한 글일 경우에만 할 수 있도록 설정한다.
[viewArticle.jsp]
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%--
<c:set var="article" value="${articleMap.article}" />
<c:set var="imageFileList" value="${articleMap.imageFileList}" />
--%>
<%
request.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>글보기</title>
<style>
#tr_file_upload {
display: none;
}
#tr_btn_modify {
display: none;
}
</style>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
function backToList(obj) {
obj.action = "${contextPath}/board/listArticles.do";
obj.submit();
}
function fn_enable(obj) {
document.getElementById("i_title").disabled = false;
document.getElementById("i_content").disabled = false;
document.getElementById("i_imageFileName").disabled = false;
document.getElementById("tr_btn_modify").style.display = "block";
document.getElementById("tr_file_upload").style.display = "block";
document.getElementById("tr_btn").style.display = "none";
}
function fn_modify_article(obj) {
obj.action = "${contextPath}/board/modArticle.do";
obj.submit();
}
function fn_remove_article(url, articleNO) {
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", url);
var articleNOInput = document.createElement("input");
articleNOInput.setAttribute("type", "hidden");
articleNOInput.setAttribute("name", "articleNO");
articleNOInput.setAttribute("value", articleNO);
form.appendChild(articleNOInput);
document.body.appendChild(form);
form.submit();
}
function fn_reply_form(url, parentNO) {
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", url);
var parentNOInput = document.createElement("input");
parentNOInput.setAttribute("type", "hidden");
parentNOInput.setAttribute("name", "parentNO");
parentNOInput.setAttribute("value", parentNO);
form.appendChild(parentNOInput);
document.body.appendChild(form);
form.submit();
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
$('#preview').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
</script>
</head>
<body>
<form name="frmArticle" method="post" action="${contextPath}" enctype="multipart/form-data">
<table border="0" align="center">
<tr>
<td width="150" align="center" bgcolor="#FF9933">
글번호
</td>
<td>
<input type="text" value="${article.articleNO }" disabled />
<input type="hidden" name="articleNO" value="${article.articleNO}" />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
작성자 아이디
</td>
<td>
<input type="text" value="${article.id }" name="writer" disabled />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
제목
</td>
<td>
<input type="text" value="${article.title }" name="title" id="i_title" disabled />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
내용
</td>
<td>
<textarea rows="20" cols="60" name="content" id="i_content" disabled />${article.content }</textarea>
</td>
</tr>
<%--
<c:if test="${not empty imageFileList && imageFileList!='null' }">
<c:forEach var="item" items="${imageFileList}" varStatus="status" >
<tr>
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지${status.count }
</td>
<td>
<input type= "hidden" name="originalFileName" value="${item.imageFileName }" />
<img src="${contextPath}/download.do?articleNO=${article.articleNO}&imageFileName=${item.imageFileName}" id="preview" /><br>
</td>
</tr>
<tr>
<td>
<input type="file" name="imageFileName " id="i_imageFileName" disabled onchange="readURL(this);" />
</td>
</tr>
</c:forEach>
</c:if>
--%>
<c:choose>
<c:when test="${not empty article.imageFileName && article.imageFileName!='null' }">
<tr>
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지
</td>
<td>
<input type="hidden" name="originalFileName" value="${article.imageFileName }" />
<img src="${contextPath}/download.do?articleNO=${article.articleNO}&imageFileName=${article.imageFileName}" id="preview" /><br>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="file" name="imageFileName " id="i_imageFileName" disabled onchange="readURL(this);" />
</td>
</tr>
</c:when>
<c:otherwise>
<tr id="tr_file_upload">
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지
</td>
<td>
<input type="hidden" name="originalFileName" value="${article.imageFileName }" />
</td>
</tr>
<tr>
<td></td>
<td>
<img id="preview" /><br>
<input type="file" name="imageFileName " id="i_imageFileName" disabled onchange="readURL(this);" />
</td>
</tr>
</c:otherwise>
</c:choose>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
등록일자
</td>
<td>
<input type=text value="<fmt:formatDate value=" ${article.writeDate}" />" disabled />
</td>
</tr>
<tr id="tr_btn_modify" align="center">
<td colspan="2">
<input type=button value="수정반영하기" onClick="fn_modify_article(frmArticle)">
<input type=button value="취소" onClick="backToList(frmArticle)">
</td>
</tr>
<tr id="tr_btn">
<td colspan="2" align="center">
<%-- 로그인 ID가 작성자 ID와 같은 경우에만 수정하기, 삭제하기 버튼이 표시된다. --%>
<c:if test="${member.id == article.id }">
<input type=button value="수정하기" onClick="fn_enable(this.form)">
<input type=button value="삭제하기" onClick="fn_remove_article('${contextPath}/board/removeArticle.do', ${article.articleNO})">
</c:if>
<input type=button value="리스트로 돌아가기" onClick="backToList(this.form)">
<input type=button value="답글쓰기" onClick="fn_reply_form('${contextPath}/board/replyForm.do', ${article.articleNO})">
</td>
</tr>
</table>
</form>
</body>
</html>
8. 다음은 실행 결과이다. 로그인하지 않았을 때와 로그인 했을 때의 결과가 어떻게 다른지 비교해 보자.
- http://localhost/pro30/main.do
(1) 로그인 하지 않았을 때
(2) 로그인 했을 때 : 수정하기와 삭제하기 버튼이 활성화되어 보임
7. 글 수정하기
* 이번에는 글을 수정할 수 있는 기능을 스프링으로 구현해 보자. 글을 추가하는 기능과 유사하므로 쉽게 이해할 수 있을 것이다.
1. 매퍼 파일에 update문을 추가한다. if문을 사용하여 글 수정 시 이미지를 수정한 경우에만 이미지 파일 이름을 업데이트 하도록 지정한다.
[board.xml]
<!-- Map으로 글 정보를 가져온다. -->
<!-- 이미지를 수정한 경우에만 이미지 파일 이름을 수정한다. -->
<update id="updateArticle" parameterType="java.util.Map">
update t_board
set title=#{title},
content=#{content}
<if test="imageFileName!='' and imageFileName!=null">
, imageFileName=#{imageFileName}
</if>
where articleNO=#{articleNO}
</update>
2. 자바 파일을 작성할 차례이다. 글 수정 시 이미지 파일도 수정해서 업로드해야 하므로 MultipartHttpServletRequest를 사용해 업로드한 후 글 정보를 articleMap에 key/value로 담아 테이블에 추가한다. 그리고 수정된 새 이미지를 글 번호 폴더에 업로드한 후에는 반드시 기존 이미지를 삭제해야 한다. 글 수정을 마친 후에는 다시 글상세창을 나타낸다.
[BoardControllerImpl.java]
@RequestMapping(value = "/board/modArticle.do", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity modArticle(MultipartHttpServletRequest multipartRequest, HttpServletResponse response)
throws Exception {
multipartRequest.setCharacterEncoding("utf-8");
Map<String, Object> articleMap = new HashMap<String, Object>();
Enumeration enu = multipartRequest.getParameterNames();
while (enu.hasMoreElements()) {
String name = (String) enu.nextElement();
String value = multipartRequest.getParameter(name);
articleMap.put(name, value);
}
String imageFileName = upload(multipartRequest);
articleMap.put("imageFileName", imageFileName);
String articleNO = (String) articleMap.get("articleNO");
String message;
ResponseEntity resEnt = null;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "text/html; charset=utf-8");
try {
boardService.modArticle(articleMap);
if (imageFileName != null && imageFileName.length() != 0) {
File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
FileUtils.moveFileToDirectory(srcFile, destDir, true); // 새로 첨부한 파일을 폴더로 이동한다.
String originalFileName = (String) articleMap.get("originalFileName");
File oldFile = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO + "\\" + originalFileName);
oldFile.delete(); // 기존 파일을 삭제한다.
}
message = "<script>";
message += " alert('글을 수정했습니다.');";
message += " location.href='" + multipartRequest.getContextPath() + "/board/viewArticle.do?articleNO="
+ articleNO + "';";
message += " </script>";
resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED);
} catch (Exception e) {
File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
srcFile.delete();
message = "<script>";
message += " alert('오류가 발생했습니다.다시 수정해주세요');";
message += " location.href='" + multipartRequest.getContextPath() + "/board/viewArticle.do?articleNO="
+ articleNO + "';";
message += " </script>";
resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED);
}
return resEnt;
}
3. Service 클래스와 DAO 클래스는 다음과 같다.
@Override
public void modArticle(Map articleMap) throws Exception {
boardDAO.updateArticle(articleMap);
}
@Override
public void updateArticle(Map articleMap) throws DataAccessException {
sqlSession.update("mapper.board.updateArticle", articleMap);
}
4. 이전에 글상세창을 구현할 때 사용한 viewArticle.jsp를 복사해 붙여 넣는다. 그리고 첨부 파일이 없는 글을 수정할 때는 파일 업로드 기능이 표시되므로 수정해야 한다.
[viewArticle.jsp]
<c:choose>
<c:when
test="${not empty article.imageFileName && article.imageFileName!='null' }">
<tr>
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지</td>
<td><input type="hidden" name="originalFileName"
value="${article.imageFileName }" /> <img
src="${contextPath}/download.do?articleNO=${article.articleNO}&imageFileName=${article.imageFileName}"
id="preview" /><br></td>
</tr>
<tr>
<td></td>
<td><input type="file" name="imageFileName "
id="i_imageFileName" disabled onchange="readURL(this);" /></td>
</tr>
</c:when>
<c:otherwise> <%-- 첨부 파일이 없는 글을 수정할 때는 파일 업로드가 표시되도록 한다. --%>
<tr id="tr_file_upload">
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지</td>
<td><input type="hidden" name="originalFileName"
value="${article.imageFileName }" /></td>
</tr>
<tr>
<td></td>
<td><img id="preview" /><br> <input type="file"
name="imageFileName " id="i_imageFileName" disabled
onchange="readURL(this);" /></td>
</tr>
</c:otherwise>
</c:choose>
5. 다음은 실행 결과이다. 글상세창에서 수정하기를 클릭하면 글을 수정할 수 있는 상태로 바뀐다. 글 내용과 이미지를 수정한 후 수정반영하기를 클릭한다.
- http://localhost/pro30/main.do
6. 그러면 다음과 같이 수정된 글 정보를 테이블에 반영하고 새로운 이미지를 업로드한 후 다시 글상세창을 나타낸다.
8. 글 삭제하기
* 이번에는 게시판에서 글을 삭제하는 기능을 추가해 보자.
1. 매퍼 파일에서 글 번호를 가져와 관련된 자식 글까지 삭제하는 delete 문을 추가한다.
[board.xml]
<!-- 글 번호를 가져온다. -->
<delete id="deleteArticle" parameterType="int">
<![CDATA[
delete from t_board
where articleNO in (
SELECT articleNO FROM t_board
START WITH articleNO = #{articleNO}
CONNECT BY PRIOR articleNO = parentNO )
]]>
</delete>
2. 컨트롤러에서는 글 번호를 가져와 해당 글을 삭제한다. 그리고 해당되는 이미지가 저장된 폴더도 함께 삭제한다.
[BoardControllerImpl.java]
@Override
@RequestMapping(value = "/board/removeArticle.do", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity removeArticle(@RequestParam("articleNO") int articleNO, // 삭제할 글 번호를 가져온다.
HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType("text/html; charset=UTF-8");
String message;
ResponseEntity resEnt = null;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "text/html; charset=utf-8");
try {
boardService.removeArticle(articleNO); // 글 번호를 전달해서 글을 삭제한다.
File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
FileUtils.deleteDirectory(destDir); // 글에 첨부된 이미지 파일이 저장된 폴더도 삭제한다.
message = "<script>";
message += " alert('글을 삭제했습니다.');";
message += " location.href='" + request.getContextPath() + "/board/listArticles.do';";
message += " </script>";
resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED);
} catch (Exception e) {
message = "<script>";
message += " alert('작업중 오류가 발생했습니다.다시 시도해 주세요.');";
message += " location.href='" + request.getContextPath() + "/board/listArticles.do';";
message += " </script>";
resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED);
e.printStackTrace();
}
return resEnt;
}
3. Service 클래스와 DAO 클래스는 다음과 같다.
@Override
public void removeArticle(int articleNO) throws Exception {
boardDAO.deleteArticle(articleNO);
}
@Override
public void deleteArticle(int articleNO) throws DataAccessException {
sqlSession.delete("mapper.board.deleteArticle", articleNO);
}
4. 다음은 실행 결과이다. 삭제하기를 클릭한다.
- http://localhost/pro30/main.do
5. 해당 글을 삭제한 후 글목록창을 다시 표시한다. 글이 삭제된 모습을 볼 수 있다.
'Java_Spring Framework part2' 카테고리의 다른 글
(15) 스프링으로 답변형 게시판 만들기 4 (0) | 2023.11.22 |
---|---|
(13) 스프링으로 답변형 게시판 만들기 2 (1) | 2023.11.21 |
(12) 스프링으로 답변형 게시판 만들기 1 (1) | 2023.11.20 |
(11) 스프링 REST API 사용하기 2 (0) | 2023.11.20 |
(10) 스프링 REST API 사용하기 1 (0) | 2023.11.16 |