본문 바로가기

Programming/국비학원

221104 - MVC - 답글 작성, 페이징 기능

  • 답글 
  • viewArticle.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<%
	request.setCharacterEncoding("utf-8");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글 상세 보기</title>
<style type="text/css">
	#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("title").disabled=false;
		document.getElementById("content").disabled=false;
		let imageFileName=document.getElementById("imageFileName");
		if (imageFileName!=null&&imageFileName!=''){
			imageFileName.disabled=false;
		}
		document.getElementById("btn_modify").style.display="block";
		document.getElementById("btn_all").style.display="none";
	}
	
	//이미지파일 첨부시 미리보기 기능 구현
	function readURL(input){
		if (input.files && input.files[0]){
			let reader=new FileReader();
			reader.onload=function(event){
				$('#preview').attr('src',event.target.result);
			}
			reader.readAsDataURL(input.files[0]);
		}
	}
	
	//수정 반영 버튼
	function fn_modify(obj){
		obj.action="${contextPath}/board/modArticle.do"
		obj.submit();
	}
	
	//삭제
	function fn_remove(url, articleNo){
		let newForm = document.createElement("form");
		newForm.setAttribute("method","post");
		newForm.setAttribute("action",url);
		
		let articleNoInput=document.createElement("input");
		articleNoInput.setAttribute("type","hidden");
		articleNoInput.setAttribute("name","articleNo");
		articleNoInput.setAttribute("value",articleNo);
		
		newForm.appendChild(articleNoInput);
		document.body.appendChild(newForm);
		newForm.submit();
	}
	
    ////replyForm.do 컨트롤러로 이동
	function fn_reply(url, parentNo){ //전달받은 글번호를 부모글번호로 사용
		let reForm=document.createElement("form");
		reForm.setAttribute("method","post");
		reForm.setAttribute("action",url); 
		
		let parentNoInput=document.createElement("input");
		parentNoInput.setAttribute("type","hidden");
		parentNoInput.setAttribute("name","parentNo");
		parentNoInput.setAttribute("value",parentNo);
		
		reForm.appendChild(parentNoInput);
		document.body.appendChild(reForm);
		reForm.submit(); //replyForm.do로 이동 (BoardController)
	}
</script>
<body>
	<form name="formArticle" method="post" enctype="multipart/form-data">
		<table align="center">
			<tr>
				<td width="150" align="center" bgcolor="beige">글번호</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="beige">아이디</td>
				<td><input type="text" name="id" id="id"
				value="${article.id}" disabled></td>
			</tr>
			<tr>
				<td width="150" align="center" bgcolor="beige">제목</td>
				<td><input type="text" name="title" id="title" value="${article.title}" disabled></td>
			</tr>			
			<tr>
				<td width="150" align="center" bgcolor="beige">내용</td>
				<td><textarea name="content" id="content" disabled>${article.content}</textarea></td>
			</tr>	
			<c:if test="${not empty article.imageFileName && article.imageFileName !='null'}">
				<tr>
					<td width="150" align="center" bgcolor="beige">이미지</td>
					<td>
						<input type="hidden" name="original" value="${article.imageFileName}">
						<img src="${contextPath}/download.do?articleNo=${article.articleNo}&
						imageFileName=${article.imageFileName}" id=preview><br> 
					</td>
				</tr>
				<tr>
					<td>
						<input colspan="2" type="file" name="imageFileName" id="imageFileName" disabled onchange="readURL(this);">
					</td>
				</tr>
			</c:if>
			<tr>
				<td width="150" align="center" bgcolor="beige">등록일자</td>
				<td>
					<input type="text" value="<fmt:formatDate value="${article.writeDate}"/>" disabled>
				</td>
			</tr>
			<tr id="btn_modify">
				<td colspan="2" align="center">
					<input type="button" value="수정 반영" onclick="fn_modify(formArticle)">
					<input type="button" value="리스트로 돌아가기" onclick="backToList(formArticle)">
					
				</td>
			</tr>
			<tr>
				<td id="btn_all">
					<input colspan="2" type="button" value="수정하기" onclick="fn_enable(this.form);">
					<input colspan="2" type="button" value="삭제하기" onclick="fn_remove('${contextPath}/board/removeArticle.do', ${article.articleNo} );">
					<input colspan="2" type="button" value="돌아가기" onclick="backToList(formArticle);">
					<input colspan="2" type="button" value="답글쓰기" onclick="fn_reply('${contextPath}/board/replyForm.do', ${article.articleNo} );"> ////						
				</td>
			</tr>
		</table>
	</form>
</body>
</html>

 

// form 속성으로 method 대신 action="post"으로 오기입해, articleNo가 전달 안 되는 오류 발생했었음 실수 유의

 

 

 

  • BoardController
@WebServlet("/board/*")
public class BoardController extends HttpServlet {

	private static String ART_IMAGE_REPO="C:\\server\\upload_images"; 
	HttpSession session; ////
	
	BoardService bs;
	ArticleVO vo;
	
	public void init(ServletConfig config) throws ServletException {
		bs= new BoardService();
		vo= new ArticleVO();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}
	
	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String nextPage = "";
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		
		String action=request.getPathInfo(); 
		System.out.println("요청명 : "+action);
		
		List<ArticleVO> articleList = new ArrayList<ArticleVO>();
		
        ~
        
        } else if (action.equals("/replyForm.do")) { ////
			
			int parentNo = Integer.parseInt(request.getParameter("parentNo")); 
                    //viewArticle 뷰에서 답글 버튼 클릭 시 fn_reply 함수 실행 
                    //-> 함수 내에서 생성한 폼 submit
                    //-> input에 setAttribute()으로 붙인 파라미터(name, value) 전달받음
			
            session = request.getSession(); ////replyForm.jsp 거쳐 BoardController 내 addReply.do로 전달 위해
			session.setAttribute("parentNo", parentNo); //부모글번호 답글 폼으로 전달
			
			nextPage="/boardView/replyForm.jsp"; ////답글 폼으로 이동
			
		}

		RequestDispatcher rd = request.getRequestDispatcher(nextPage);
		rd.forward(request, response);
		
	}
	
		
	//자료,이미지 업로드 처리 메소드
	private Map<String, String> upload(HttpServletRequest request, HttpServletResponse response) {
		Map<String, String> articleMap = new HashMap<String, String>();
		String encoding = "utf-8";
		File currentDirPath = new File(ART_IMAGE_REPO); //
		
		DiskFileItemFactory factory = new DiskFileItemFactory(); //파일 업로드, 저장할 저장소
		factory.setRepository(currentDirPath); //저장소 위치 설정
		factory.setSizeThreshold(1024*1024); //저장할 최대 크기
		
		ServletFileUpload upload = new ServletFileUpload(factory);
		
		try {
			List items = upload.parseRequest(request); //요청 객체로부터 받은 HTTP Body 변환, List 반환
			
			for (int i=0;i<items.size();i++) {
				FileItem fileItem = (FileItem) items.get(i);
				//fileitem : 업로드한 파일 or input에 입력된 텍스트
			
				if (fileItem.isFormField()) { //파일 형식 X (텍스트 등)
					System.out.println(fileItem.getFieldName()+" : "+fileItem.getString(encoding));
					articleMap.put(fileItem.getFieldName(), fileItem.getString(encoding));
				} else { //파일 형식 O
					System.out.println("파라미터 이름 : "+fileItem.getFieldName());
					System.out.println("파일 이름 : "+fileItem.getName());
					System.out.println("파일 크기 : "+fileItem.getSize()+"바이트");
					
					if (fileItem.getSize()>0) {
						//String separator=File.separator; //OS별 구분자
						//int index = fileItem.getName().lastIndexOf(separator); //마지막에 위치한 구분자 인덱스						
						String fileName = fileItem.getName();
								//.substring(index+1); //구분자 뒤 텍스트 가져옴 (파일명)
						System.out.println("filename : "+fileName);
						
						articleMap.put(fileItem.getFieldName(), fileName); //getFieldName : 요청파라미터명
						File uploadFile = new File(currentDirPath + "/temp/" + fileName); //파일 객체 생성 ////
						fileItem.write(uploadFile); //실제로 저장소에 파일 생성, 저장
					}
				}
			}
			
		} catch (Exception e) {
			System.out.println("업로드 중 에러");
			e.printStackTrace();
		}
		
		return articleMap;
	}
	
}

 

 

 

  • replyForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>답글쓰기</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
	//글쓰기 완료 => 목록으로 돌아가기
	function listView(obj){
		obj.action="${contextPath}/board/listArticles.do";
		obj.submit();
	}
	
	//이미지파일 첨부시 미리보기 기능
	function readURL(input){
		if (input.files && input.files[0]){
			let reader=new FileReader();
			reader.onload=function(event){
				$('#preview').attr('src',event.target.result);
			}
			reader.readAsDataURL(input.files[0]);
		}
	}
</script>
</head>
<body>
	<h2 align="center">답글쓰기</h2>
	<form name="formReply" action="${contextPath}/board/addReply.do" ////BoardController 이동
	method="post" enctype="multipart/form-data">
		<table align="center">
			<tr>
				<td align="right">글쓴이 </td>
				<td><input type="text" value="chanho" disabled></td>
			</tr>
			<tr>
				<td align="right">글제목 </td>
				<td><input type="text" name="title"></td>
			</tr>			
			<tr>
				<td align="right">글내용 </td>
				<td><textarea rows="10" cols="50" maxlength="4000" name="content"></textarea></td>
			</tr>	
			<tr>
				<td align="right">이미지 파일 첨부 </td>
				<td><input type="file" name="imageFileName" onchange="readURL(this);"></td>
				<td><img id="preview" src="#" width="200" height="200"></td>
			</tr>						
			<tr>
				<td align="center" colspan="2">
					<input type="submit" value="답글 반영">
					<input type="button" value="취소" onclick="listView(this.form);">
				</td>
			</tr>
		</table>
	</form>
</body>
</html>

 

 

 

 

  • BoardController
		} else if (action.equals("/addReply.do")) { //답변 submit
			
			session=request.getSession(); ////
			int parentNo = (Integer) session.getAttribute("parentNo"); 
            //BoardController -> replyForm.do 거쳐 온 세션값
			session.removeAttribute("parentNo");
			
			Map<String, String> articleMap = upload(request,response);
			
			String title = articleMap.get("title");
			String content = articleMap.get("content");
			String imageFileName = articleMap.get("imageFileName");
			
			vo.setParentNo(parentNo);
			vo.setId("chanho");
			vo.setTitle(title);
			vo.setContent(content);
			vo.setImageFileName(imageFileName);
			
			int articleNo = bs.addReply(vo); 
            ////BoardService로 부모글번호 & 인풋값 세팅한 vo 객체 전달 => 글 번호 받아옴
            
			if (imageFileName != null && imageFileName.length() != 0) {
				File srcFile = new File(ART_IMAGE_REPO+"/temp/"+imageFileName);
				File destFile = new File(ART_IMAGE_REPO+"/"+articleNo);
				destFile.mkdir();
				FileUtils.moveFileToDirectory(srcFile, destFile, true);
			}
			
			PrintWriter pw = response.getWriter();
			pw.print("<script>"
					+ "alert('답글을 추가했습니다');"
					+ "location.href='"+request.getContextPath()+"/board/viewArticle.do?"
					+ "articleNo=" + articleNo + "';"
					+ "</script>");
			
			return;
			
		}

 

 

 

  • BoardService
	public int addReply(ArticleVO vo) {
		return dao.insertNewArticle(vo);
	}

 

 

 

  • BoardDAO
	//새글 번호 생성 메소드
	private int getNewArticleNo() {
		try {
			con=dataFactory.getConnection();
            
			String query = "select max(articleNo) from board_qna"; //현재 글번호 중 가장 큰 숫자
			System.out.println(query);
			ps = con.prepareStatement(query);
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				return rs.getInt(1)+1;
			} 
		}catch(Exception e) {
			System.out.println("글 번호 생성 중 에러");
		}
		return 1; //글번호 없으면 1 부여
	}

    //글쓰기 저장 메소드
	public int insertNewArticle(ArticleVO vo) { //void->int
		int articleNo = getNewArticleNo(); //
		try {
			con=dataFactory.getConnection();
			
			
			int parentNo = vo.getParentNo();
			String title = vo.getTitle();
			String content = vo.getContent();
			String id = vo.getId();
			String imageFileName = vo.getImageFileName();
			
			String query = "insert into board_qna(articleno, parentno, title, content, id, imagefile)"
					+ "values(?,?,?,?,?,?)";
			System.out.println(query);
			
			ps=con.prepareStatement(query);
			ps.setInt(1, articleNo);
			ps.setInt(2, parentNo);
			ps.setString(3, title);
			ps.setString(4, content);
			ps.setString(5, id);
			ps.setString(6, imageFileName);
			ps.executeUpdate();
			
			ps.close();
			con.close();
		} catch (Exception e) {
			System.out.println("DB 글쓰기 저장 중 에러");
		}
		
		return articleNo; //
		
	}

 

 

 

 

  • 페이징
  • BoardController
		if (action==null||action.equals("/listArticles.do")) {
			
			////페이징 기능
			String _section = request.getParameter("section"); 
			String _pageNum = request.getParameter("pageNum");
                        //첫 방문시 section, pageNum 파라미터 null -> 1로 값세팅
			int section = Integer.parseInt((_section)==null?"1":_section);
			int pageNum = Integer.parseInt((_pageNum)==null?"1":_pageNum);
			
			Map<String, Integer> pagingMap = new HashMap<String, Integer>();
			pagingMap.put("section", section);
			pagingMap.put("pageNum", pageNum);
			
			Map articleMap = bs.listArticles(pagingMap); ////페이징 처리한 글목록, 전체 게시글 수 리턴
			articleMap.put("section", section);
			articleMap.put("pageNum", pageNum);
			
			request.setAttribute("articleMap", articleMap);
			
			articleList = bs.listArticles(); ////전체 글 목록
			request.setAttribute("articleList", articleList);
			
			nextPage="/boardView/listArticles.jsp"; 
			
		}

 

 

 

  • BoardService
    ////섹션, 페이지 값 통해 페이징 처리한 글목록, 전체 게시글 수 리턴
    public Map listArticles(Map<String, Integer> pagingMap) { //섹션, 페이지 값 파라미터로 전달됨
		
		Map articleMap = new HashMap();
		List<ArticleVO> articleList = dao.selectAllArticles(pagingMap); ////페이징 처리한 글 목록
		int totArticles = dao.selectTotArticles(); ////전체 게시글 수
		articleMap.put("articleList", articleList);
		articleMap.put("totArticles", totArticles);
		
		return articleMap;
	}
    
    //전체 글 목록
	public List<ArticleVO> listArticles(){
		List<ArticleVO> articleList = dao.selectAllArticles();
		return articleList;
	}

 

 

 

  • BoardDAO
	////페이징 - 섹션당 100개, 페이지당 10개 게시글 표시 (메소드 오버로딩)
	public List selectAllArticles(Map pagingMap){
		List articleList = new ArrayList<ArticleVO>();
		
		//페이징 기능 추가
		int section = (Integer) pagingMap.get("section"); //섹션 값
		int pageNum = (Integer) pagingMap.get("pageNum"); //페이지 값
		
		try {
			
			con=dataFactory.getConnection();
			/*
			 * String query = "select * from (select rownum as recnum from board_qna" +
			 * " order by articleno desc)" +
			 * " where recnum between (?-1)*100 + (?-1)*10+1 and (?-1)*100 + ?*10";
			 * ps=con.prepareStatement(query); ps.setInt(1, section); //11페이지부터 ps.setInt(2,
			 * pageNum); //페이지 수 ps.setInt(3, section); ps.setInt(4, pageNum);
			 */
             
                        // 1. 기존 계층형 구조로 글 목록 조회
                        // 2. 그 결과에 대해 다시 ROWNUM(recNum)이 표시되도록 서브 쿼리문으로 다시 조회
                        // 3. ROWNUM이 표시된 두 번째 결과에서 where 조건에 맞는 ROWNUM이 있는 레코드들만 최종적 조회
			String query = "SELECT * FROM (SELECT rownum as recnum, lvl, articleno,"
					+ " parentno, title, id, writedate FROM (SELECT level as lvl, articleno,"
					+ " parentNo, title, id, writedate FROM board_qna START WITH parentno=0"
					+ " CONNECT BY PRIOR articleno=parentno ORDER SIBLINGS BY articleno DESC))"
					+ " where recnum between (?-1)*100 + (?-1)*10+1 and (?-1)*100 + ?*10";
			System.out.println(query);
			
			ps=con.prepareStatement(query);
			ps.setInt(1, section); 
			ps.setInt(2, pageNum); 
			ps.setInt(3, section); 
			ps.setInt(4, pageNum);
			
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				int level = rs.getInt("lvl");
				int articleNo = rs.getInt("articleno");
				int parentNo = rs.getInt("parentno");
				String title = rs.getString("title");
				String id = rs.getString("id");
				Date writeDate = rs.getDate("writedate");
				
				ArticleVO vo = new ArticleVO();
				vo.setArticleNo(articleNo);
				vo.setLevel(level);
				vo.setParentNo(parentNo);
				vo.setTitle(title);
				vo.setId(id);
				vo.setWriteDate(writeDate);
				
				articleList.add(vo);
			}
			
		} catch (Exception e) {
			System.out.println("DB 페이징 처리 중 에러");
		}
		
		return articleList;
		
	}
	

	//전체 게시글 수
	public int selectTotArticles() {
		
		try {
			con=dataFactory.getConnection();
			String query = "select count(articleno) from board_qna";
			System.out.println(query);
			
			ps=con.prepareStatement(query);
			ResultSet rs = ps.executeQuery();
			if (rs.next()) {
				return rs.getInt(1);
			}
			
			rs.close();
			ps.close();
			con.close();
		} catch(Exception e) {
			System.out.println("DB 전체 게시글 수 처리 중 에러");
		}
		
		return 0;
		
	}
    
    
	//게시판 글 목록
	public List<ArticleVO> selectAllArticles(){
		List<ArticleVO> articleList = new ArrayList<ArticleVO>();
		
		try {
			
			con=dataFactory.getConnection();
			//오라클 - 계층형 쿼리
			String query="SELECT LEVEL, articleNo, parentNo, title, content, id, writeDate from board_qna"
					+ " START WITH parentNo=0 CONNECT BY PRIOR articleNo=parentNo"
					+ " ORDER SIBLINGS BY articleNo DESC";
			System.out.println(query);
			ps=con.prepareStatement(query);
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				int level = rs.getInt(1);
				int articleNo = rs.getInt(2);
				int parentNo = rs.getInt(3);
				String title = rs.getString(4);
				String content = rs.getString(5);
				String id = rs.getString(6);
				Date writeDate = rs.getDate(7);
				
				ArticleVO article = new ArticleVO();
				article.setLevel(level);
				article.setArticleNo(articleNo);
				article.setParentNo(parentNo);
				article.setTitle(title);
				article.setContent(content);
				article.setId(id);
				article.setWriteDate(writeDate);

				articleList.add(article);
			}
			
			rs.close();
			ps.close();
			con.close();
			
		} catch(Exception e) {
			
			System.out.println("DB 글목록 조회 중 에러");
			
		}
		
		return articleList;
	}

 

 

 

  • listArticles.jsp 수정
<c:set var="articleList" value="${articleMap.articleList}"/>  ////페이징 처리한 글 목록
<c:set var="totArticles" value="${articleMap.totArticles}"/>  ////전체 게시글 수
<c:set var="section" value="${articleMap.section}"/>
<c:set var="pageNum" value="${articleMap.pageNum}"/>


~

<style>
 .sel_page{
 	color:red;
 }
a {
	text-decoration:none;
	color:black;
}
</style>


~

	<div>
	 <c:if test="${totArticles != null }" >
      <c:choose>
		<c:when test="${totArticles>100}"> <!-- 글 개수 100개 초과 -->
			<c:forEach var="page" begin="1" end="10" step="1">
				<c:if test="${section>1&&page==1}">
					<a href="${contextPath}/board/listArticles.do?section=${section-1}
					&pageNum=${(section-1)*10+1}">prev</a> <!-- 이전 섹션 -->
				</c:if>
				<a href="${contextPath}/board/listArticles.do?section=${section}&pageNum=${page}">
				${(section-1)*10+page}</a>
				<c:if test="${page==10}">
					<a href="${contextPath}/board/listArticles.do?section=${section+1}
					&pageNum=${section*10+1}">next</a> <!-- 다음 섹션 -->
				</c:if>
			</c:forEach>
		</c:when>
		<c:when test="${totArticles==100}"> <!-- 글 개수 100개 -->
			<c:forEach var="page" begin="1" end="10" step="1">
				<a href="#">${page}</a>
			</c:forEach>
		</c:when>
		<c:when test="${totArticles<100}"><!-- 글 개수 100개 미만 -->
			<c:forEach var="page" begin="1" end="${totArticles/10+1}" step="1" >
				<c:choose>
					<c:when test="${page==pageNum}">
						<a class="sel_page" href="${contextPath}/board/listArticles.do?
						section=${section}&pageNum=${page}">${page}</a>
					</c:when>
					<c:otherwise>
						<a class="no_page" href="${contextPath}/board/listArticles.do?
						section=${section}&pageNum=${page}">${page}</a>
					</c:otherwise>
				</c:choose>
			</c:forEach>
		</c:when>	
	 </c:choose>
    </c:if>		
	</div>

 

 

 

※ MemberVO 오류 수정

 

이미지 없는 글 조회시 imageFileName 에 예전 값이 그대로 들어가 숨김처리한 태그가 보여지는 오류 발생

 

	public void setImageFileName(String imageFileName) {
		try {
			if (imageFileName != null && imageFileName.length()!=0) { 
				this.imageFileName=URLEncoder.encode(imageFileName,"utf-8"); 
			} else { ////
				this.imageFileName=null; ////
			}
		} catch(UnsupportedEncodingException e) {
			System.out.println("이미지 파일 이름 저장 중 에러");
		}
	}

 

setter에  else문을 누락해 발생했었음