Controller (Servlet) 클래스
- WebServlet 클래스 등은 Apache Tomcat 의 servlet-api.jar 파일에 있다
흐름 정리
- 클라이언트가 요청하면 무조건 Controller 클래스로 가고, 적절한 Service 클래스로 넘겨준다 거기서 DAO 의 메소드를 호출하여 DB와 연동
- DAO 에서 검색한 결과를 Service 클래스에서 공유설정하고 Controller 클래스에서 포워딩해서 View 에서 공유된 값을 가져와서 출력
문제점 1
- 등록 버튼을 눌러서 글을 등록한 뒤 목록페이지에서 F5(새로고침) 를 누르면 똑같은 데이터가 추가된다
* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료
- 새로고침을 누를때마다 하나씩 추가됨
문제점 1 해결
- BoardAddAction.java (원문 글 작성 Service 클래스) 에서 setRedirect(false) 를 true 로 설정해서 Redirect 방식으로 포워딩하면 이 문제가 해결된다
문제점 2
- 글 작성 성공 후 글 목록 가져오기를 "/BoardListAction.do" 로 요청할때 get이 나와야하는데 post 방식이 나온다
- 포워딩을 하면 get 방식으로 전달되지만, 콘솔창에는 post 방식으로 뜬다
+ 폼으로 /BoardListAction.do 로 오는게 아니므로 post 가 아닌 get 방식이다
문제점 2 이유
- 위는 글 작성 요청 아래는 글 작성이 끝난 후 글 목록을 가져오는 요청
- dispatcher 방식으로 포워딩되면 URL 주소가 바뀌지 않으므로 이전에 글 작성할때 사용했던 post 방식으로 나옴
문제점 1 & 문제점 2 해결 방법
- setRedirect 를 true 로 바꿈
- setPath 에서 혹시 경로를 못찾아가면 . 를 앞에 붙여줌
* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료
- 새로고침해도 같은 데이터가 들어가지 않는다
문제점 정리
- Get 방식처럼 단순히 조회하는 요청은 Dispatcher 방식을 사용
- Post, Put 방식처럼 무언가를 변화시키는 요청에는 Redirect 방식을 사용
- Dispatcher 방식이 Redirect 방식보다 성능상 좋다.
- Post, Put 같은 무언가를 변화시키는 요청에는 Redirect 방식을 사용
- 클라이언트가 Post 요청으로 무언가를 변화시킨 후 새로고침을 눌러버리면 웹 브라우저에서 새로고침은 마지막에 서버에 전송한 데이터를 다시 전송하기 때문에 다시 URL에 접속되면서 Post 요청이 한번 더 전송되기 때문
ex) 상품 등록 요청을 하였는데 재요청을 보내버리면 2개의 상품이 중복 등록가 돼버리는 상황이 발생하게 된다.
다른 페이지로 이동
- 아직 페이지를 선택할 수 있는 부분을 만들지 않았으므로 URL 에 입력해서 찾아가보자
- 이 페이지에 맞는 10 개의 글(데이터) 을 뿌려줌
파생변수 number
<c:set var="num" value="${listcount - (page - 1) * 10}"/>
- 각 페이지에 출력될 시작 번호를 저장하고 있다
- 마지막의 10 은 한 페이지에 출력할 데이터 개수 limit 를 의미한다
- 그 번호에서 반복문으로 1씩 감소하여서 출력
더미 데이터 만들기
- 페이지 처리 구현시 보기 위해 더미 데이터 여러개를 만든다
* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료
게시판 프로그램 : 목록 가져오기 (이어서)
- 목록가져오는 것은 구현했고, 공유된 리스트, 기본변수, 파생변수들을 View 에서 가져와서 출력하는 과정에 있다
- 목록은 뿌렸고 페이지 처리, 페이지 선택할 수 있는 것 만들고 있다
- qna_board_list.jsp (최종)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>
<%
int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %> 개 <br>
<table border=1 width=700 align=center>
<caption>게시판 목록</caption>
<tr>
<td>번호</td>
<td>제목</td>
<td>작성자</td>
<td>날짜</td>
<td>조회수</td>
</tr>
<c:set var="num" value="${listcount - (page - 1) * 10}"/>
<c:forEach var="b" items="${boardlist}">
<tr>
<td> ${num}
<c:set var="num" value="${num-1}"/>
</td>
<td>
<!-- 댓글 제목 앞에 여백 처리 -->
<c:if test="${b.board_re_lev > 0}">
<c:forEach var="i" begin="0" end="${b.board_re_lev}">
</c:forEach>
</c:if>
<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_subject}</a>
</td>
<td>
${b.board_name}
</td>
<td>
<fmt:formatDate value="${b.board_date}"
pattern="yyyy-MM-dd HH:mm:ss EEEE"/>
</td>
<td>${b.board_readcount}</td>
</tr>
</c:forEach>
</table> <br><br>
<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">
<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>
<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>
<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
<c:if test="${i == page}"> <!-- 현재 페이지 -->
[${i}]
</c:if>
<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
<a href="./BoardListAction.do?page=${i}">[${i}]</a>
</c:if>
</c:forEach>
<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>
<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>
</c:if>
</center>
페이지 처리 / 페이지 메뉴 바 설계
각 블럭에 10개의 페이지 출력 (qna_board_list.jsp 부분)
<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
<c:if test="${i == page}"> <!-- 현재 페이지 -->
[${i}]
</c:if>
<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
<a href="./BoardListAction.do?page=${i}">[${i}]</a>
</c:if>
</c:forEach>
- forEach 문으로 startPage 와 endPage 까지 반복문을 돌려서 1 2 3 ... 10 로 페이지 선택하는 걸 만듬
- 현재 페이지가 아닌 경우 클릭한 페이지 번호를 넘기면서 "/BoardListAction.do" 로 요청한다
- 이러면 Controller 클래스를 다시 찾아가서 BoardListAction.java Service 클래스로 가서 다시 목록을 가져온다
- 이때 BoardListAction.java 의 아래 코드에서
if(request.getParameter("page") != null) {
page = Integer.parseInt(request.getParameter("page"));
}
- 사용자가 선택한 페이지가 page 변수에 저장되고 그걸 이용해서 데이터를 가져옴
- 그리고 page 변수로부터 유도된 변수 및 리스트를 BoardListAction.java 아래에서 공유설정함
// 공유 설정
request.setAttribute("page", page);
- 그걸 다시 View qna_board_list.jsp 에서 받는다
첫 페이지로 이동, 마지막 페이지로 이동 (qna_board_list.jsp 부분)
<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>
- 1 page 로 이동
<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>
- 총 페이지 수 == 마지막 페이지
- pageCount 는 총 페이지 수이고, 공유되어 받아온 값이다
- >> 클릭시 마지막 페이지로 이동
* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료
이전 블럭 (qna_board_list.jsp)
<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>
- 블럭단위로 이동
- startPage 는 본인이 속한 블럭의 첫 페이지 의미
- startPage > 10 은 '1 페이지가 아니면' 이라는 의미, ${startPage - 10} 은 현재 블럭의 첫 페이지에서 -10 을 하면 이전 블럭의 첫 페이지로 이동
ex) 13 페이지에 있다가 [이전] 클릭시 현재 블럭 startPage 인 11 에서 -10 을 하여 1 페이지로 이동, 즉 1 블럭의 startPage 로 이동
다음 블럭 이동 (qna_board_list.jsp)
<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>
- endPage 는 본인이 속한 블럭의 끝 페이지를 의미
- endPage < pageCount 은 '마지막 페이지가 아니면' 이라는 의미, ${startPage + 10} 은 현재 블럭의 첫 페이지에서 +10 을 하면 다음 블럭의 첫 페이지로 이동
블럭과 파생변수 startPage, endPage
- 두번째 블럭의 startPage 는 11, endPage 는 20
- 페이지를 클릭할때마다
- 페이지 번호를 누를때마다 계속 DB와 연동해서 목록을 가져오는 작업, request 로 공유하는 작업, View 에서 받아서 뿌리는 작업이 수행된다
+ 같은 페이지 처리를 JSTL 쓰지 않고 했을때
- Model 1 게시판 페이지 메뉴바 만드는 코드이다 : https://laker99.tistory.com/117
<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
// pageCount : 총 페이지 수
int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);
System.out.println("pageCount : " + pageCount);
// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
// endPage : 각 블럭의 끝 페이지 번호 : 10, 20, 30 ...
int startPage = ((currentPage-1)/10) * 10 + 1;
int block = 10; // 1개의 블럭의 크기 : 10개의 페이지로 구성
int endPage = startPage + block - 1;
// 가장 마지막 블럭에 endPage값을 pageCount로 수정
if(endPage > pageCount) { //
endPage = pageCount;
}
%>
<!-- 1page로 이동 -->
<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
// 이전 블럭으로 이동
if(startPage > 10){ %>
<a href="list.jsp?page=<%=startPage-10 %>">[이전]</a>
<% }
// 각 블럭당 10개의 페이지 출력
for(int i = startPage; i <= endPage; i++) {
if(i == currentPage) { %>
[<%=i %>]
<% } else {%>
<a href="list.jsp?page=<%=i %>">[<%=i %>]</a>
<% }
}// for end
// 다음 블럭으로 이동
if(endPage < pageCount) {
%>
<a href="list.jsp?page=<%=startPage+10 %>">[다음]</a>
<% } %>
<!-- 마지막 페이지로 이동 -->
<a href="list.jsp?page=<%=pageCount %>" style="text-decoration:none"> >> </a>
<%}%>
</center>
게시판 프로그램 : 상세 페이지
- 제목 클릭시 상세 페이지로 넘어간다
- 글번호 num 과 페이지 번호를 전달해야한다 ( 총 3번 전달 중 첫번째)
- 목록페이지 -> 상세페이지 -> 수정/삭제 폼 -> 수정/삭제
- 처음 2 번은 get 방식으로 전달, 마지막은 post 로 전달
- qna_board_list.jsp 부분
<td>
<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>
- 상세 페이지로 가기 위해 /BoardDetailAction.do 로 요청한다
게시판 프로그램 : 상세 페이지 Service 클래스
- service 패키지 안에 상세 페이지 Service 클래스인 BoardDetailAction.java 를 생성 및 작성하자
- BoardDetailAction.java (수정 전)
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardDetailAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("BoardDetailAction");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
BoardDAO dao = BoardDAO.getInstance();
dao.readcountUpdate(board_num); // 조회수 1 증가
BoardBean board = dao.getDetail(board_num); // 상세정보 구하기
// 글내용 줄바꿈 기능
String content = board.getBoard_content().replace("\n", "<br>");
// 공유 설정
request.setAttribute("board", board);
request.setAttribute("content", content);
request.setAttribute("page", page);
ActionForward forward = new ActionForward();
forward.setRedirect(false); // dispatcher 방식으로 포워딩
forward.setPath("./board/qna_board_view.jsp");
return forward;
}
}
- 목록 페이지에서 제목을 클릭해서 넘어왔고, 글번호와 페이지번호가 넘어왔다, 그 값을 변수 board_num 과 page 로 받음
- 글번호는 int 형으로 형변환한다
<조회수 1 증가>
- 조회수 값을 1 증가시키는 update SQL 을 수행하는 메소드 readcountUpdate() 가 필요하다
- 그 글의 조회수 컬럼을 1 증가시켜야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함
- 따로 리턴을 받을 필요는 없다, 아무것도 리턴 받지 않고 있음
* DAO 클래스 readcountUpdate() 메소드 작성은 아래에
<상세 정보 구하기>
- 상세 정보를 구해오는 메소드 select SQL 을 수행하는 메소드 getDetail() 이 필요하다
- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함
- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO
* DAO 클래스 getDetail() 메소드 작성은 아래에
<DAO -> Service 로 돌아왔을때> (BoardDetailAction.java 부분)
// 공유 설정
request.setAttribute("board", board);
request.setAttribute("content", content);
request.setAttribute("page", page);
- View 페이지로 돌아간다, 돌아갈때 2가지 변수 글번호, 페이지번호 뿐 아니라 검색된 결과를 돌려받은 객체 board 도 가져가야한다
- 이때, 객체 board, 줄바꿈 처리를 한 content , 페이지번호 page 3개를 공유설정한다
- 글번호 num 은 board 객체 안의 컬럼 board_num 에 있음
- content 는 board 에서 가져와서 줄바꿈 처리를 한 것이다
- 상세페이지 출력 View 페이지인 qna_board_view.jsp 파일로 포워딩한다.
- 이 Service 클래스를 호출했던 Controller 클래스로 돌아감
Model 1 vs Model 2
- Model 1 에서는 두 기능을 하나의 메소드에 처리했다, Model 2 에서는 따로 메소드를 만듬
+ MyBatis 로 바꿔서 처리하면 1개의 메소드 내에서 2개의 sql 문 사용하기 힘들어지므로 지금부터 다른 메소드로 처리
Controller 클래스에서 상세 페이지 Service 클래스 BoardDetailAction로 전달 (BoardFrontController.java 부분)
- BoardFrontController.java 전체 코드는 나중에
- BoardFrontController.java 부분
- BoardDetailAction.do 로 요청이 올때의 경우를 처리
// 상세 페이지
} else if(command.equals("/BoardDetailAction.do")) {
try {
action = new BoardDetailAction();
forward = action.execute(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
- 연결 이후 Service 클래스까지 잘 넘어간다
DAO 클래스 조회수 1 증가 메소드 작성
- BoardDAO.java 전체 코드는 모두 구현 후 올리기
- BoardDAO.java 추가된 readcountUpdate() 부분 코드만
// 조회수 1 증가
public void readcountUpdate(int board_num) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
String sql = "update model2 set board_readcount = board_readcount + 1 ";
sql += "where board_num = ?";
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, board_num);
pstmt.executeUpdate(); // SQL문 실행
} catch(Exception e) {
e.printStackTrace();
} finally {
if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
if(con != null) try { con.close(); } catch(Exception e) {}
}
}
- 데이터를 돌려줄 필요없으므로 리턴 자료형 void
- 그 글의 조회수 컬럼을 1 증가시켜야하므로 그 글을 특정할 수 있는 글번호 board_num 이 전달되고, 그걸로 SQL문 where 절 작성
DAO 클래스 상세정보 구하기 메소드 작성
- BoardDAO.java 전체 코드는 모두 구현 후 올리기
- BoardDAO.java 추가된 getDetail() 부분 코드만
// 상세 페이지
public BoardBean getDetail(int board_num) {
BoardBean board = new BoardBean();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
String sql = "select * from model2 where board_num = ?";
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, board_num);
rs = pstmt.executeQuery(); // SQL문 실행
if(rs.next()) {
board.setBoard_num(rs.getInt("board_num"));
board.setBoard_name(rs.getString("board_name"));
board.setBoard_pass(rs.getString("board_pass"));
board.setBoard_subject(rs.getString("board_subject"));
board.setBoard_content(rs.getString("board_content"));
board.setBoard_file(rs.getString("board_file"));
board.setBoard_re_ref(rs.getInt("board_re_ref"));
board.setBoard_re_lev(rs.getInt("board_re_lev"));
board.setBoard_re_seq(rs.getInt("board_re_seq"));
board.setBoard_readcount(rs.getInt("board_readcount"));
board.setBoard_date(rs.getTimestamp("board_date"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(rs != null) try { rs.close(); } catch(Exception e) {}
if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
if(con != null) try { con.close(); } catch(Exception e) {}
}
return board;
}
- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함
- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO
- select 문을 작성하므로 ResultSet rs 객체를 생성하고 그 객체로 검색된 데이터들을 받는다
- DTO 객체 board 를 생성하고, 그 board 에 검색된 데이터를 setter 메소드로 세팅한 후 리턴한다
- 다음은 board 폴더 하위에 상세 페이지를 출력하는 View 페이지인 qna_board_view.jsp 파일을 생성
- qna_board_view.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">
</head>
<body>
<table border=1 width=400 align=center>
<caption>상세 페이지</caption>
<tr>
<td>제목</td>
<td>${board.board_subject}</td>
</tr>
<tr>
<td>내용</td>
<td>
<pre>${board.board_content}</pre>
${content}
</td>
</tr>
<tr>
<td>첨부파일</td>
<td>
<!-- 첨부파일이 있을때 첨부파일 출력 -->
<c:if test="${board.board_file != null}">
<a href="./board/file_down.jsp?file_name=${board.board_file}">
${board.board_file}
</a>
</c:if>
</td>
</tr>
<tr>
<td colspan=2 align=center>
<input type="button" value="댓글">
<input type="button" value="수정">
<input type="button" value="삭제">
<input type="button" value="목록"
onClick="location.href='./BoardListAction.do?page=${page}'">
</td>
</tr>
</table>
</body>
</html>
<EL>
- Service 클래스인 BoardDetailAction.java 에서 board 객체를 공유설정했으므로 ${board.board_subject} 처럼 네임값.필드명 으로 간단히 출력 가능
<내용 출력>
- 내용 content 을 출력하는 방법 2가지를 모두 썻다
1. 공유된 content 를 바로 EL 로 출력
2. 공유된 board 객체에서 board.board_content 로 내용을 가져와서 <pre></pre> 로 감싸기
<첨부파일> (qna_board_view.jsp 부분)
<!-- 첨부파일이 있을때 첨부파일 출력 -->
<c:if test="${board.board_file != null}">
<a href="./board/file_down.jsp?file_name=${board.board_file}">
${board.board_file}
</a>
</c:if>
- if 태그로 첨부파일이 null 이 아니면, 즉 첨부파일이 있으면 첨부파일명을 출력
- 첨부파일명에 링크를 걸어 클릭시 첨부파일을 다운받도록 한다 첨부파일명을 클릭시 file_down.jsp 로 이동
- file_down.jsp 로 이동하며 다운로드 받을 파일명 ${board.board_file} 을 file_name 변수에 담아 전달한다
<목록 버튼>
- '목록' 버튼 클릭시 "/BoardListAction.do" 요청을 하면서 현제 페이지 번호를 목록 구하는 곳으로 전달한다
- page 변수에 값이 저장되었으므로 원래 페이지로 돌아갈 수 있다
<댓글 버튼>
- '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 하면서 글번호 board_num 과 원래 페이지번호 page 를 전달
- 특정 글 아래에 댓글을 달게되므로 글번호 board_num 이 필요하다
- 댓글 작성 완료 후 원래의 페이지로 돌아가기 위해서 page 번호가 필요하다
- 나머지 버튼들은 나중에 링크 연결하기
Q. 왜 파일 다운하기 위해 file_down.jsp 로 갈떄는 원문 글 작성폼으로 갈때와 달리 Controller 안거치고 jsp 로 가는지 모르겠다
A. 원칙은 Controller 클래스 거쳐서 가야하지만 이 file_down.jsp 파일은 예외적으로 바로 JSP 파일로 가기
게시판 프로그램 : 댓글 작성
- 상세페이지에서 '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 get 방식으로 한다
- Controller 클래스에서 이 요청을 처리하고 댓글 작성폼 Service 클래스인 BoardReplyAction.java 로 가야함
게시판 프로그램 : 댓글 작성폼 Service 클래스
- 댓글 작성폼으로 가기 전, 부모글의 정보가 필요하고, 부모글의 정보를 가져올떄 DB 연동을 해야하므로 필요한 Service 클래스이다
- 댓글 작성폼으로 이동 전 필요하므로 이름을 "댓글 작성폼 Service 클래스" 로 한다
- service 패키지 안에 BoardReplyAction.java 클래스 생성 및 작성
- BoardReplyAction.java
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardReplyAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("BoardReplyAction");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
BoardDAO dao = BoardDAO.getInstance();
// 부모글의 상세 정보 구하기
BoardBean board = dao.getDetail(board_num);
// 공유 설정
request.setAttribute("board", board);
request.setAttribute("page", page);
ActionForward forward = new ActionForward();
forward.setRedirect(false); // dispatcher 방식으로 포워딩
forward.setPath("./board/qna_board_reply.jsp");
return forward;
}
}
- 상세 페이지 View 인 qna_board_view.jsp 에서 넘어온 글번호 board_name 와 페이지번호를 받는다
- 댓글 처리 관련 변수 3개를 처리하기 위해서는 부모글의 상세 정보 (변수 3개)를 먼저 구해야하므로 getDetail() 사용
- getDetail() 의 매개변수로 부모 글의 번호가 될 board_name 를 전달한다
ex) 부모의 board_re_lev 에서 + 1 을 하는 등 부모의 정보 필요
- DB에서 받아온 부모 글의 정보를 저장한 객체 board 와 페이지번호 page 를 공유설정한다.
- 댓글 작성폼 View 페이지 qna_board_reply.jsp 로 포워딩한다, request 로 공유설정했으므로 dispatcher 방식으로 포워딩
Controller 클래스에서 댓글 작성폼 Service 클래스 BoardReplyAction로 전달 (BoardFrontController.java 부분)
- BoardFrontController.java 전체 코드는 나중에
- BoardFrontController.java 부분
- BoardReplyAction.do 로 요청이 올때의 경우를 처리
// 댓글 폼
} else if(command.equals("/BoardReplyAction.do")) {
try {
action = new BoardReplyAction();
forward = action.execute(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
- 댓글 작성폼 qna_board_reply.jsp 를 생성 및 작성하자
- qna_board_reply.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>댓글 게시판</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">
<input type="hidden" name="board_re_ref" value="${board.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${board.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${board.board_re_seq}">
<table cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">댓글 게시판</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">글쓴이</div>
</td>
<td>
<input name="board_name" id="board_name" type="text" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">제 목</div>
</td>
<td>
<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100"
value="re."/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">내 용</div>
</td>
<td>
<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="댓글">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- qna_board_write.jsp 의 내용 복붙 후 수정
- 첨부파일 부분을 삭제, enctype 도 삭제하고, action 값을 "/BoardReply.do" 로 수정
- 부모글의 댓글 관련 컬럼 3개와, 글번호, 페이지번호 총 5개를 hidden 으로 값을 전달해야한다
- 그리고 댓글 작성 폼에 입력된 값들도 전달
- 글번호는 부모글의 글 번호를 의미, 그래서 공유된 부모글 정보 객체인 board 에서 부모글의 글번호를 가져옴
게시판 프로그램 : 댓글 작성 Service 클래스
- 실제로 댓글을 작성하는 역할을 한다
- service 패키지 안에 BoardReply.java 클래스 생성 및 작성
- BoardReply.java
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardReply implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("BoardReply");
request.setCharacterEncoding("utf-8");
int board_num = Integer.parseInt(request.getParameter("board_num"));
int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
String page = request.getParameter("page");
BoardBean board = new BoardBean();
board.setBoard_num(board_num);
board.setBoard_re_ref(board_re_ref);
board.setBoard_re_lev(board_re_lev);
board.setBoard_re_seq(board_re_seq);
board.setBoard_name(request.getParameter("board_name"));
board.setBoard_pass(request.getParameter("board_pass"));
board.setBoard_subject(request.getParameter("board_subject"));
board.setBoard_content(request.getParameter("board_content"));
BoardDAO dao = BoardDAO.getInstance();
int result = dao.boardReply(board); // 댓글 작성
if(result == 1) System.out.println("댓글 작성 성공");
ActionForward forward = new ActionForward();
forward.setRedirect(true);
forward.setPath("./BoardListAction.do?page="+page);
return forward;
}
}
- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩
- hidden 으로 넘어온 5개 값과 댓글 작성 입력 양식에서 넘어온 값들도 request.getParameter() 로 받는다
- 생성한 DTO 객체 board에 받아온 값들( 부모 글 정보 + 댓글 작성 입력양식에 사용자가 작성한 값들) 을 setter 메소드로 세팅한다
- 부모 글에 관한 정보를 저장한 변수들은 SQL문 안에서 사용될것이므로 int 형으로 변환한다
- 댓글 작성 메소드인 boardReply() 메소드를 호출하면서 부모 글 정보와 입력한 데이터가 담긴 board 객체를 매개변수로 전달해줌
- 원래 페이지로 돌아가기 위해 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩 하면서 get 방식으로 현재 페이지 번호를 넘겨준다, 이로 인해 원래 페이지로 돌아갈 수 있음
* DAO 클래스의 boardReply() 작성은 아래에
Controller 클래스에서 댓글 작성 Service 클래스 BoardReply로 전달 (BoardFrontController.java 부분)
- BoardFrontController.java 전체 코드는 나중에
- BoardFrontController.java 부분
- BoardReply.do 로 요청이 올때의 경우를 처리
// 댓글 작성
} else if(command.equals("/BoardReply.do")) {
try {
action = new BoardReply();
forward = action.execute(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
DAO 클래스 댓글 작성 메소드 작성
- BoardDAO.java 전체 코드는 모두 구현 후 올리기
- BoardDAO.java 추가된 boardReply() 부분 코드만
// 댓글 작성
public int boardReply(BoardBean board) {
int result = 0;
Connection con = null;
PreparedStatement pstmt = null;
// 부모글에 대한 정보
int re_ref = board.getBoard_re_ref(); // 글 그룹 번호
int re_lev = board.getBoard_re_lev(); // 댓글의 깊이
int re_seq = board.getBoard_re_seq(); // 댓글의 출력순서
try {
con = getConnection();
String sql = "update model2 set board_re_seq = board_re_seq + 1";
sql += " where board_re_ref = ? and board_re_seq > ?";
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, re_ref);
pstmt.setInt(2, re_seq);
pstmt.executeUpdate(); // SQL문 실행
sql = "insert into model2 values(model2_seq.nextval,";
sql += "?,?,?,?,?,?,?,?,?,sysdate)";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, board.getBoard_name());
pstmt.setString(2, board.getBoard_pass());
pstmt.setString(3, board.getBoard_subject());
pstmt.setString(4, board.getBoard_content());
pstmt.setString(5, ""); // board_file
pstmt.setInt(6, re_ref); // board_re_ref
pstmt.setInt(7, re_lev+1); // board_re_lev
pstmt.setInt(8, re_seq+1); // board_re_seq
pstmt.setInt(9, 0); // board_readcount
result = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
if(con != null) try { con.close(); } catch(Exception e) {}
}
return result;
}
- 매개변수로는 부모글의 정보와 댓글 작성 폼에 적힌 데이터가 저장된 DTO 객체 board 가 넘어온다
- 부모글에 대한 3가지 정보를 변수에 저장
- 2가지 SQL 문을 사용해야 한다
<update 문>
- 댓글의 board_re_seq 는 자신의 부모글 보다 seq 값이 큰 글들만 seq 값을 1 씩 증가시킴
1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가
2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가
<insert 문>
- 댓글 번호는 마찬가지로 sequence 로 넣는다
- 댓글의 board_re_ref 는 부모글의 board_re_ref 와 일치해야함
- 댓글의 board_re_lev 는 부모글의 board_re_lev 보다 1 커야함
- board_re_lev 와 board_re_seq 는 부모 글의 board_re_lev 와 board_re_seq 에서 1 증가한 값을 넣음
- 이제 상세 페이지에서 '댓글' 을 클릭해서 댓글 폼으로 간 후 댓글을 작성하자
문제점
- 안나온다
- 또 get 으로 전송하는데도 post 가 나옴
문제 해결
- BoardReply.java 의 setRedirect() 를 true 로 바꾸기
* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료
- 댓글 작성 후 목록페이지가 잘 나타난다
- 이제 댓글과 대댓글을 더 많이 써보자
게시판 프로그램 : 글 수정
- 수정 폼으로 넘어가기 전 DAO 에서 글의 상세정보를 불러와야한다
- 그러므로 "/BoardModifyAction.do" 로 요청함
- 상세 페이지 하단에서 '수정' 눌렀을때 수정폼 Service 클래스로 넘어가기 위해 "/BoardModifyAction" 로 요청
- qna_board_view.jsp 부분
<input type="button" value="수정"
onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">
- 상세페이지에서 글번호 board_num 과 페이지번호 page 가 넘어온다
게시판 프로그램 : 글 수정폼 Service 클래스
- service 패키지 안에 글 수정폼 Service 클래스인 BoardModifyAction.java 를 생성 및 작성하자
- BoardModifyAction.java
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import model.BoardBean;
public class BoardModifyAction implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("BoardModifyAction");
int board_num = Integer.parseInt(request.getParameter("board_num"));
String page = request.getParameter("page");
BoardDAO dao = BoardDAO.getInstance();
// 상세 정보 구하기
BoardBean board = dao.getDetail(board_num);
// 공유 설정
request.setAttribute("board", board);
request.setAttribute("page", page);
ActionForward forward = new ActionForward();
forward.setRedirect(false); // dispatcher 방식으로 포워딩
forward.setPath("./board/qna_board_modify.jsp");
return forward;
}
}
- 수정폼으로 가기 전에 상세정보를 먼저 구해야 한다, 그 후에 수정폼으로 포워딩해야함
- 가장 먼저 상세페이지에서 get 방식으로 넘어온 글번호 board_num 과 페이지번호 page 를 가져온다
- DAO 의 getDetail() 메소드로 board_num 을 사용해서 글의 상세정보를 가져온다
- 가져온 상세정보를 받은 객체 board 를 공유설정하고, 페이지 번호 page 도 공유설정한다
+ 글 번호는 객체 board 안에도 있으므로 따로 공유설정 하지 않음
Controller 클래스에서 글 수정폼 Service 클래스 BoardModifyAction로 전달 (BoardFrontController.java 부분)
- BoardFrontController.java 전체 코드는 나중에
- BoardFrontController.java 부분
- BoardModifyAction.do 로 요청이 올때의 경우를 처리
// 수정 폼
} else if(command.equals("/BoardModifyAction.do")) {
try {
action = new BoardModifyAction();
forward = action.execute(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
- 다음은 글 수정폼 View 페이지인 qna_board_modify.jsp 파일을 생성하자
- qna_board_modify.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>게시판 수정</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">
<table cellpadding="0" cellspacing="0" align=center border=1>
<tr align="center" valign="middle">
<td colspan="5">게시판 수정</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">글쓴이</div>
</td>
<td>
<input name="board_name" id="board_name" type="text" size="10" maxlength="10"
value="${board.board_name}"/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">비밀번호</div>
</td>
<td>
<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10"
value=""/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12" height="16">
<div align="center">제 목</div>
</td>
<td>
<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100"
value="${board.board_subject}"/>
</td>
</tr>
<tr>
<td style="font-family:돋음; font-size:12">
<div align="center">내 용</div>
</td>
<td>
<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
</td>
</tr>
<tr bgcolor="cccccc">
<td colspan="2" style="height:1px;">
</td>
</tr>
<tr><td colspan="2"> </td></tr>
<tr align="center" valign="middle">
<td colspan="5">
<input type=submit value="수정">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- qna_board_reply.jsp 파일의 내용을 복붙 후 수정
- 수정폼에 수정할 정보를 입력하고 '수정' 버튼 클릭시 글 수정 요청인 "/BoardModify.do" 로 요청이 간다
- hidden 으로는 글 번호인 board_num 과 페이지번호 page 가 넘어간다
- 글 수정 요청인 "/BoardModify.do" 로 가면서 hidden 으로 2개, 입력양식으로 4개의 값들, 총 6개의 값이 글 수정 Service 클래스 BoardModify.java 로 전달됨
- Service 클래스 BoardModifyAction.java 에서 공유했던 board 객체에서 이름을 "${board.board_name}" 로 가져와서 입력양식에 value 속성으로 넣기
- 이름 뿐 아니라 제목, 내용도 마찬가지로 처리한다
- 구현 후 상세페이지에서 '수정' 클릭시 나타나는 수정폼에 정보들이 나타남
- 비밀번호를 입력하고 '수정' 버튼 클릭시 /BoardModify.do 로 요청함
- 실제 수정을 처리해주는 Service 클래스인 BoardModifyjava 클래스를 생성하자
게시판 프로그램 : 글 수정 Service 클래스
- service 패키지 안에 글 수정 Service 클래스인 BoardModifyjava.java 를 생성 및 작성하자
- BoardModify.java (수정 전 1)
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BoardModify implements Action{
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("BoardModify");
ActionForward forward = new ActionForward();
return forward;
}
}
- 아직 작성 안했고 여기까지 도착했는지 출력만 하고 있다, 내일 작성
Controller 클래스에서 글 수정 Service 클래스 BoardModify로 전달 (BoardFrontController.java 부분)
- BoardFrontController.java 전체 코드는 나중에
- BoardFrontController.java 부분
- BoardModify.do 로 요청이 올때의 경우를 처리
// 글 수정
} else if(command.equals("/BoardModify.do")) {
try {
action = new BoardModify();
forward = action.execute(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}