JSP/Servlet - 게시판 만들기!

게시판 만들기

지금까지 배운 JSP 문법으로 게시판을 하나 만들어보자.

MVC패턴을 적용하지 않을거고 DB는 저번 JDBC구조를 사용하자.

https://kouzie.github.io/jdbc/JDBC.-4일차/

MVC패턴은 적용하지 않을 것이고 DB에서 데이터를 가져오는 Servlet클래스,
데이터를 출력하는 .jsp파일을 기능에 맞추어 하나씩 생성할 것이다.

게시판 출력

먼저 게시판 글목록을 가져오는 List.java서블릿 클래스와 list.jsp파일을 생성하자.

<a href="/jspPro/board/list">글목록</a><br/>
List.java서블릿 클래스를 web.xml(배포서술자)에 위 주소와 같이 등록하자.

게시판용 서블릿 객체는 모두 /contextRoot/board에 위치시킬 것이다.

<servlet>
  <servlet-name>listServlet</servlet-name>
  <servlet-class>days05.List</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>listServlet</servlet-name>
  <url-pattern>/board/list</url-pattern>
</servlet-mapping>

위의 링크태그를 클릭하면 List.javadoGet()메서드를 호출하게 되고

doGet()에선 DB에서 게시판 목록을 SELECT쿼리로 읽어온 후 DTOrequest객체에 담아 list.jsp로 포워딩 시킨다.

DB연결 참고: https://kouzie.github.io/jsp/JSP-doGet,-Servelet-LifeCycle,-request,-response,-URL-Mapping/#db연결-서블릿-객체-생성

/* List.java doGet() 메서드 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("List doGet() called....");
  
  int curPage=1; //현재페이지
  int pageSize = 15; //뿌릴 게시글 개수
  
  int start = (curPage - 1)* pageSize + 1 ;
  int end = curPage * pageSize;
  
  StringBuffer sql = new StringBuffer();
  sql.append(" WITH temp AS( ");
  sql.append(" SELECT ROWNUM AS no, temp.* ");
  sql.append(" FROM ");
  sql.append(" ( ");
  sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
  sql.append("    FROM tbl_board ");
  sql.append("    ORDER BY seq desc ");
  sql.append("    )temp ");
  sql.append(" ) ");
  sql.append(" SELECT temp.* FROM temp ");
  sql.append(" WHERE temp.no BETWEEN ? AND ? ");
  
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  ArrayList<MyBoardDTO> blist = null;
  
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    pstmt.setInt(1,  start);
    pstmt.setInt(2,  end);
    
    rs = pstmt.executeQuery();
    if(rs.next())
    {
      MyBoardDTO mdto = null;
      blist = new ArrayList<>();
      do {
        mdto = new MyBoardDTO();
        mdto.setNo(rs.getInt("no"));
        mdto.setSeq(rs.getInt("seq"));
        mdto.setName(rs.getString("name"));
        mdto.setEmail(rs.getString("email"));
        mdto.setSubject(rs.getString("subject"));
        mdto.setCnt(rs.getInt("cnt"));
        mdto.setRegDate(rs.getDate("regdate"));
        blist.add(mdto);
      } while (rs.next());
    }
    rs.close();
    pstmt.close();
  } catch (Exception e) {
    System.out.println(e);
  }
  
  String path = "/days05/list.jsp";
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);
  request.setAttribute("list", blist);
  dipatcher.forward(request, response);
  //list를 띄우기 위한 서블릿, jsp로 포워드시킨다.
}

request.setAttribute("list", blist)를 통해 요청객체(request)에 DTO List를 저장하고

dipatcher.forward(request, response)로 jsp파일로 포워딩 한다.

출력할 게시판 리스트를 전달받은 list.jsp는 아래와 같이 코어태그를 사용해서 반복출력하기만 하면 된다.

참고: 위에선 rs.getDate("regdate")를 통해 날짜 객체를 가져왔다.
sql.Date객체는 밀리세컨드 까지 포함된 크기가 커다란 데이터인데 시간단위까지 출력할 필요가 없다면 TO_CHAR(regdate, 'yyyy/MM/dd') regdate sql함수를 통해 문자열 형식으로 받는 것 이 효율적이다.

<!-- list.jsp -->
<c:if test="${not empty list }">
  <c:forEach items="${list }" var="dto">
    <tr align="center">
      <td>${dto.seq }</td>
      <td>
        <a href="content?seq=${dto.seq }">${dto.subject }</a>
      </td>
      <td> 
        <a href="mailto:${dto.email }">${dto.name }</a>
      </td> 
      <td>${dto.regDate }</td>
      <td>${dto.cnt }</td>
    </tr>	
  </c:forEach>
</c:if>

image05

페이징 처리는 밑에서…

게시판 쓰기

게시판 아래의 글쓰기를 클릭하면 <a href="regist">글쓰기</a>를 클릭하면 게시글 작성 가능한 페이지로 이동하도록 하자.

/jspPro/board/regist/ url-mapping을 가진 서블릿 클래스를 생성하도록 하자.

<!-- web.xml 배포서술자, regist객체 매핑 -->
<servlet>
  <servlet-name>registServlet</servlet-name>
  <servlet-class>days05.Regist</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>registServlet</servlet-name>
  <url-pattern>/board/regist</url-pattern>
</servlet-mapping>

Regist서블릿 클래스의 특징은 doGet()메서드와 doPost()메서드가 하는역할이 다르다는 것!

맨 처음 a태그를 클릭하면 url이동이기 때문에 doGet()을 호출하게 되고
서블릿 클래스의 doGet()에서 바로 regist.jsp파일로 포워딩시킨다.

/* Regist.java doGet()메서드 */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Regist doGet() called....");
  String path = "/days05/regist.jsp";
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);
  dipatcher.forward(request, response);
}

포워딩 되기때문에 클라이언트의 브라우저가 띄우는 url/localhost/jspPro/board/regist.jsp가 아닌 /localhost/jspPro/board/regist이다.

regist.jsp에선 다음과 같이 입력을 위한 input태그들이 정의되어 있다.

<!-- regist.jsp -->
<form name="registForm" method= "post" action="">
<table style="border: solid 1px gray; padding:2px; width:500px" >
	<tr>
		<td colspan="2" align="center"><b>글을 적어주세요</b></td>
	</tr>
	<tr>
		<td align="center">이름</td>
		<td><input type="text" name="name" size="15"></td>
	</tr>
	<tr>
		<td align="center">비밀번호</td>
		<td><input type="password" name="password" size="15"></td>
	</tr>
	<tr>
		<td align="center">Email</td>
		<td><input type="text" name="email" size="50"></td>
	</tr>
	<tr>
		<td align="center">제목</td>
		<td><input type="text" name="subject" size="50"></td>
	</tr>
	<tr>
		<td align="center">내용</td>
		<td><textarea name="content" cols="50" rows="10"></textarea></td>
	</tr>
	<tr>
		<td align="center">HTML</td>
		<td>
			<input type="radio" name="tag" value="T" checked>적용
			<input type="radio" name="tag" value= "F">비적용
		</td>
	</tr>
	<tr>
		<td colspan="2" align="center">
			<input type="submit" value="작성 완료">&nbsp;&nbsp;&nbsp;
			<input type="reset" value="다시 작성">
		</td>
	</tr>
</table>
</form>

image06

여기서 주의할 것은 form태그의 속성이다.

post방식으로 전송하는데 action속성이 비어있다! <form name="registForm" method= "post" action="">

action속성이 없다는 것은 자기 자신 urlpost하겠다는 뜻,

Regist.java 서블릿 클래스가 포워딩을 통해 regist.jsp로 이동시켰기 때문에 현재 클라이언트의 주소는 a태그로 이동했던 주소 localhost/jspPro/board/regist 그대로다.

위의 urlpost했기 때문에 Regist.java 서블릿 클래스의 doPost()메서드를 호출하게 된다.

이런식으로 doGet()은 jsp파일로 이동을,
doPost()는 DB와 연결해 INSERT쿼리를 수행하도록 한다.

/*Regist.java doPost()메서드*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Regist doPost() called....");
  request.setCharacterEncoding("utf-8");
  
  String name = request.getParameter("name");
  String password = request.getParameter("password");
  String email = request.getParameter("email");
  String subject = request.getParameter("subject");
  String content = request.getParameter("content");
  String tag = request.getParameter("tag");
  
  MyBoardDTO bdto = new MyBoardDTO();
  bdto.setName(name);
  bdto.setPassword(password);
  bdto.setEmail(email);
  bdto.setSubject(subject);
  bdto.setContent(content);
  bdto.setTag(tag);
  
  StringBuffer sql = new StringBuffer();
  sql.append(" INSERT INTO tbl_board ");
  sql.append(" (seq, name, password, email, subject, content, tag, userip) ");
  sql.append(" VALUES(seq_myboard.nextval, ?, ?, ?, ?, ?, ?, '127.0.0.1') ");
  
  String state = "";
  //INSERT 성공, 실패여부를 클라이언트에 알려주기 위한 파라미터용 변수
  Connection conn = null;
  PreparedStatement pstmt;
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    pstmt.setString(1, bdto.getName());
    pstmt.setString(2, bdto.getPassword());
    pstmt.setString(3, bdto.getEmail());
    pstmt.setString(4, bdto.getSubject());
    pstmt.setString(5, bdto.getContent());
    pstmt.setString(6, bdto.getTag().equals("T")?"y":"n");
    int result = 0;
    result = pstmt.executeUpdate();
    if(result == 1)
      state = "success";
    else
      state = "fail";
    pstmt.close();
    DBConn.close();
  } catch (Exception e) {
    state = "Insert fail";
  }
  String location = "/jspPro/board/list?insert="+state;
  response.sendRedirect(location );
}

입력이 완료되면 성공, 실패 결과인 stateurl뒤에 붙여 List.java서블릿 클래스로 리다이렉트 시킨다.

List.javadoGet()을 호출해서 list.jsp로 다시 포워딩 시키게 되고
list.jsp에선 JS로 다음과 같이 INSERT 성공 실패 여부를 띄어준다.

한글이 깨지기 때문에 읽어오는 파라미터를 모두 utf-8로 디코딩 하도록 하자 request.setCharacterEncoding("utf-8")

/* list.jsp <script> */
var insert_st = "${param.insert}";
if(insert_st == "success")
{
  alert("Insert " + insert_st);
}
else if (insert_st == "fail") {
  alert("Insert " + insert_st);
}

image07

doGet()doPost()가 다른 역할을 하고
.java로 코딩한 Servlet클래스와 .jsp로 코딩한 제스퍼 서블릿 클래스가 서로 다른 업무 분담되어 있는 것을 주의하자.

게시글 보기

위의 게시글 리스트를 띄울때 아래코딩을 통해 모든 게시글 제목에 링크를 달아 놓았다.

<a href="content?seq=${dto.seq }">${dto.subject }</a>

링크 주소를 보면 알겠지만 /jspPro/board/content/ url-mapping을 가진 서블릿 클래스(Content.java)를 만들어야 한다.

게시글 보기는 딱히 doGetdoPost의 작업을 나눌 필요 없다.(그냥 조회수를 1증가시키고 출력만 시키면 되기 때문에)

물론 데이터를 가져오는 것은 Content.java에서,
데이터를 출력하는 것은 content.jsp에서 분담한다.

/* Content.java doGet()메서드 */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Content doGet() called....");

Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  MyBoardDTO mdto  = null;
  StringBuffer sql = new StringBuffer();
  sql.append(" SELECT seq, name, email, subject, content, cnt, regdate ");
  sql.append(" FROM tbl_board ");
  sql.append(" WHERE seq = ? ");

  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    int seq = Integer.parseInt(request.getParameter("seq"));
    pstmt.setInt(1, seq );
    increaseCnt(seq); //조회수 증가
    rs = pstmt.executeQuery();

    if(rs.next())
    {
      mdto = new MyBoardDTO();
      mdto.setSeq(rs.getInt("seq"));
      mdto.setName(rs.getString("name"));
      mdto.setEmail(rs.getString("email"));
      mdto.setSubject(rs.getString("subject"));
      mdto.setContent(rs.getString("content"));
      mdto.setRegDate(rs.getDate("regdate"));
      mdto.setCnt(rs.getInt("cnt"));
    }
    rs.close();
    pstmt.close();
  } catch (Exception e) {
    System.out.println(e);
  }
  String path = "/days05/content.jsp";
  request.setAttribute("dto", mdto);
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);
  dipatcher.forward(request, response);
}

Content.java서블릿 클래스 역시 content.jsp로 출력할 DTO객체를 가지고 포워딩한다.

그리고 SELECT쿼리를 수행하기 전에 조회수를 증가시키는 increaseCnt()메서드를 호출하는데 코드는 아래와 같다.

public int increaseCnt(int seq) throws SQLException {
  Connection conn = null;
  PreparedStatement pstmt = null;
  StringBuffer sql = new StringBuffer();
  sql.append(" UPDATE tbl_board ");
  sql.append(" SET cnt = cnt+1 ");
  sql.append(" WHERE seq = ? ");
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    pstmt.setInt(1, seq);
    int resultCnt = pstmt.executeUpdate();
    pstmt.close();
    return resultCnt;
  } catch (Exception e) {
    // TODO: handle exception
  }
  return seq;
}

간단한 UPDATE쿼리를 수행한다.

출력은 위한 content.jsp코드는 아래와 같다.

<div align="center">
	<table>
		<tr>
			<td>이름</td>
			<td>${dto.name }</td>
			<td>등록일</td>
			<td>${dto.regDate }</td>
		</tr>
		<tr>
			<td>Email</td>
			<td><a href="mailto:${dto.email }">${dto.email }</a></td>
			<td>조회</td>
			<td>${dto.cnt }</td>
		</tr>
		<tr>
			<td>제목</td>
			<td colspan="3">${dto.subject }</td>
		</tr>
		<tr>
			<td colspan="4" style="padding:15px;">${dto.content }</td>
		</tr>
		<tr>
			<td colspan="4" align="center">
				<a href="/jspPro/board/edit?seq=${dto.seq }">수정하기</a>
				<a href="/jspPro/board/list">목록으로</a>
				<a href="/jspPro/board/delete?seq=${dto.seq }">삭제하기</a>
			</td>
		</tr>
	</table>
</div>

image08

이제 수정, 삭제기능을 구현하면 게시판의 기본적인 기능은 끝이다!

게시글 수정하기

<a href="/jspPro/board/edit?seq=${dto.seq }">수정하기</a>

이제는 url만 봐도 어떤 서블릿 객체가 필요한지,
doGet(), doPost()의 역할을 나누어야 하는지 알 수 있다.

Edit.java서블릿 클래스가 필요하고 게시글 작성(Regist)때 처럼 doGet()doPost()메서드 기능이 나뉠 필요가 있다.

doGet()은 수정할 수 있는 페이지(edit.jsp)로 포워딩 하는 기능이,

doPost()는 DB와 연결해 받은 데이터로 UPDATE시키는 기능을 구현해야 한다.

url뒤에 seq=${dto.seq }를 붙여 get방식으로 Edit.java서블릿 클래스를 호출하기 때문에

doGet()메서드에선 받은 seq를 가지고 DTO객체를 SELECT해와서 수정 페이지인 edit.jsp로 포워딩한다.

/* Edit.java doGet()메서드 */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Edit doGet() called....");
  
  request.setCharacterEncoding("utf-8");
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  MyBoardDTO mdto  = null;
  StringBuffer sql = new StringBuffer();
  sql.append(" SELECT seq, name, email, subject, content, cnt, tag, regdate ");
  sql.append(" FROM tbl_board ");
  sql.append(" WHERE seq = ? ");

  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    int seq = Integer.parseInt(request.getParameter("seq"));
    pstmt.setInt(1, seq );
    rs = pstmt.executeQuery();

    if(rs.next())
    {
      mdto = new MyBoardDTO();
      mdto.setSeq(rs.getInt("seq"));
      mdto.setName(rs.getString("name"));
      mdto.setEmail(rs.getString("email"));
      mdto.setSubject(rs.getString("subject"));
      mdto.setContent(rs.getString("content"));
      mdto.setRegDate(rs.getDate("regdate"));
      mdto.setTag(rs.getString("tag")=="y" ? "T" : "F");
      mdto.setCnt(rs.getInt("cnt"));
    }
    rs.close();
    pstmt.close();
    
  } catch (Exception e) {
    System.out.println(e);
  }
  String path = "/days05/edit.jsp";
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);
  request.setAttribute("dto", mdto);
  dipatcher.forward(request, response);
}

edit.jspcontent.jsp에서 출력하던 값을 input태그에 넣을뿐 다를 것이 없다.

<!-- edit.jsp -->
<form name="editForm" method="post" action="">
	<input type="hidden" name="seq" value="${dto.seq }" />
	<table>
		<tr>
			<td colspan="2" align="center"><b>글을 수정합니다</b></td>
		</tr>
		<tr>
			<td align="center">이름</td>
			<td>
        ${dto.name }
			</td>
		</tr>
		<tr>
			<td align="center">Email</td>
			<td>
				${dto.email }
			</td>
		</tr>
		<tr>
			<td align="center">제목</td>
			<td>
				${dto.subject }
			</td>
		</tr>
		<tr>
			<td align="center">내용</td>
			<td>
				<textarea name="content" cols="50" rows="10">${dto.content }</textarea>
			</td>
		</tr>
		<tr>
			<td align="center">HTML</td>
			<td>
				<input type="radio" name="tag" value="T" >적용
				<input type="radio" name="tag" value="F" >비적용
				<script>
					$(":radio[value='${dto.tag }']").prop("checked", true);
				</script>
			</td>
		</tr>
		 <tr>
			<td align="center">비밀번호</td>
			<td>
				<input type="password" name="password" size="15">
			</td>
		</tr>
		<tr>
			<td colspan="2" align="center">
				<input type="submit" value="작성 완료">&nbsp;&nbsp;
				<input type="button" onClick="location.href = '/jspPro/board/content?seq=${param.seq }'" value="이전으로">
			</td>
		</tr>
	</table>
</form>

image09

Edit.java에서 포워딩을 통해 edit.jsp로 이동했기 때문에 url주소는 그대로다.
/localhost/jspPro/board/edit?seq=581

또한 form태그의 action속성이 비어있다는 것을 주의하자.

이상태에서 submit하면 Edit.javadoPost()메서드를 호출하게 된다.

/* Edit.java doPost()메서드 */
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Edit doPost() called....");
  request.setCharacterEncoding("utf-8");
  String password = request.getParameter("password");
  String subject = request.getParameter("subject");
  String content = request.getParameter("content");
  String tag = request.getParameter("tag");
  int seq = Integer.parseInt(request.getParameter("seq"));
  
  StringBuffer sql1 = new StringBuffer();
  sql1.append("SELECT password FROM tbl_board WHERE seq = ?");
  
  StringBuffer sql2 = new StringBuffer();
  sql2.append(" UPDATE tbl_board ");
  sql2.append(" SET subject = ?, content = ?, tag = ?");
  sql2.append(" WHERE seq = ? ");
  String state = "fail";
  Connection conn = null;
  PreparedStatement pstmt;
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql1.toString());
    pstmt.setInt(1, seq);
    ResultSet rs = null;
    rs = pstmt.executeQuery();
    rs.next();
    String o_password = rs.getString("password");
    rs.close();

    if(o_password.equals(password))
    {
      //password 일치시
      pstmt = conn.prepareStatement(sql2.toString());
      pstmt.setString(1, subject);
      pstmt.setString(2, content);
      pstmt.setString(3, tag.equals("T") ? "y" : "n");
      pstmt.setInt(4, seq);
      int result = 0;
      result = pstmt.executeUpdate();
      if(result == 1)
        state = "success";
      else
        state = "fail";
    }
    else
    {
      //password 불일치
      pstmt.close();
      DBConn.close();
      System.out.println(state);
      request.setAttribute("update", state);
      doGet(request, response);
      return;
    }
    pstmt.close();
    DBConn.close();
    
  } catch (Exception e) {
    System.out.println(e);
    state = "fail";
  }
  String location = "/jspPro/board/content?seq="+ seq +"&update="+state;
  response.sendRedirect(location);
}

먼저 password를 읽어와 검사하고 패스워드가 일치하지 않는다면 request.setAttribute("update", state)을 통해 UPDATE실패 결과를 파라미터에 담아 doGet()메서드 호출,

doGet()은 다시 update.jsp로 포워딩 시킨다.

update.jsp는 클라이언트에게 update결과를 알려주기 위해 js를 통해 alert를 띄운다.

/* update.jsp */
$(document).ready(function (){
  var udate_st = "${update}";
  if(udate_st == "success")
  {
    alert("Update " + udate_st);
  }
  else if (udate_st == "fail") {
    alert("Update " + udate_st);
  }
});

패스워드가 일치한다면 UPDATE쿼리를 수행하고 Content.java서블릿 클래스로 update결과값과 seq값을 가지고 클라이언트를 리다이렉트 시킨다.

content.jsp에서도 클라이언트에게 update결과값을 알려주기 위해 위의 똑같은 js코딩이 들어간다.

/* content.jsp */
$(document).ready(function (){
  var udate_st = "${update}";
  if(udate_st == "success")
  {
    alert("Update " + udate_st);
  }
  else if (udate_st == "fail") {
    alert("Update " + udate_st);
  }
});

image10

게시글 삭제

삭제는 수정보다 더 간단하다.

비밀번호를 확인하고 DELETE 쿼리를 수행하면 된다.
삭제하기의 링크주소는 아래와 같다.
<a href="/jspPro/board/delete?seq=${dto.seq }">삭제하기</a>

/board/delete url-mappingDelete.java서블릿 클래스가 필요하고
삭제를 위한 비밀번호 입력용 페이지인 delete.jsp가 필요하다.

또한 처음엔 비밀번호 입력을 위해 doGet()을 통해 delete.jsp으로 포워딩을 통해 이동하고

입력한 비밀번호을 DB에서 비교하기 위해 doPost()를 호출한다.

Delete.java서블릿 클래스에서도 doGet()doPost()기능이 나뉜다.

맨처음 a태그를 클릭하면 a태그를 통해 링크로 이동하기 때문에 Delete.javadoGet()메서드가 호출된다.

/* Delete.java doGet()메서드 */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Delete doGet() called....");
  String path = "/days05/delete.jsp";
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);
  dipatcher.forward(request, response);
}

삭제용 비밀번호를 입력받는 delete.jsp로 이동!

포워딩 임으로 url주소는 /localhost/jspPro/board/delete?seq=581에서 변동 없다.

<!-- delete.jsp -->
<form name="deleteForm" method="post" action=""> <!-- Delete.java의 doPost전달 -->
	<input type="hidden" name="seq" value="${param.seq }"/>
	<table>
		<tr>
			<td colspan="2" align="center"><b>글을 삭제합니다</b></td>
		</tr>
		<tr>
			<td align="center">비밀번호</td>
			<td>
				<input type="password" name="password" size="15" autofocus="autofocus">
			</td>
		</tr>
		<tr>
			<td colspan="2" align="center">
				<input type="submit" value="삭제">&nbsp;&nbsp;
				<input type="button" onClick="location.href = '/jspPro/board/content?seq=${param.seq }'" value="취소">
			</td>
		</tr>
	</table>
</form>

image11

취소를 누르면 다시 Content.javadoGet()메서드를 호출 함으로 content.jsp페이지도 포워딩한다.

확인을 누르면 form태그의 action으로 post하게 되는데 비어있음으로 현재 자신url로 post요청 함으로 Delete.javadoPost()를 호출한다.

doPost()에선 당연히 입력받은 비밀번호가 일치하는지 확인하고 DELETE쿼리를 수행한다.

/* Delete.java doPost()메서드 */
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("Delete doPost() called....");
  request.setCharacterEncoding("utf-8");
  int seq = Integer.parseInt(request.getParameter("seq"));
  String password = request.getParameter("password");

  StringBuffer sql1 = new StringBuffer();
  sql1.append("SELECT password FROM tbl_board WHERE seq = ?");
  
  StringBuffer sql2 = new StringBuffer();
  sql2.append(" DELETE FROM tbl_board WHERE seq = ? ");
  
  String state = "fail";
  Connection conn = null;
  PreparedStatement pstmt;
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql1.toString());
    pstmt.setInt(1, seq);
    ResultSet rs = null;
    rs = pstmt.executeQuery();
    rs.next();
    String o_password = rs.getString("password");
    rs.close();
    
    if(o_password.equals(password))
    {
      //password가 일치할 경우
      pstmt = conn.prepareStatement(sql2.toString());
      pstmt.setInt(1, seq);
      int result = 0;
      result = pstmt.executeUpdate();
      System.out.println("pstmt2실행 완료");
      if(result == 1)
        state = "success";
      else
        state = "fail";
    }
    else
    {
      //password가 일치하지 않을 경우
      pstmt.close();
      DBConn.close();
      request.setAttribute("delete", state);
      doGet(request, response);
      return;
    }
  } catch (Exception e) {
    state = "fail";
  }
  pstmt.close();
  DBConn.close();

  String location = "/jspPro/board/list?delete="+state;
  response.sendRedirect(location );
}

마찬가지로 실패할 경우 delete결과값을 파라미터로 request객체에 저장하고 doGet()메서드를 호출한다.
다른 jsp파일과 마찬가지로 delete.jsp에도 delete결과를 alert로 띄어준다.

/* delete.jsp */
var delete_st = "${delete}"
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}

패스워드가 일치할 경우 delete성공 여부를 들고 List.java서블릿 클래스로 리다이렉트 시킨다.

List.java에선 List.jsp로 포워딩 됨으로 List.jspdelete결과를 alert시킬 js코드가 있어야 한다.

/* list.jsp */
var insert_st = "${param.insert}";
if(insert_st == "success")
{
  alert("Insert " + insert_st);
}
else if (insert_st == "fail") {
  alert("Insert " + insert_st);
}

var delete_st = "${param.delete}";
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}

주의: ${param.delete}${delete}

EL사용시 헷갈릴 만한 사항이 있다.

list.jsp에선 ${param.delete}형식으로 delete값을 가져오고
delete.jsp에선 ${delete}형식으로 delete값을 가져온다.

/* list.jsp */
var delete_st = "${param.delete}";
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}
...
/* delete.jsp */
var delete_st = "${delete}"
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}

게시판 리스트 페이징 처리

사실 위에서 게시판 리스트를 가져오는 쿼리는 이미 페이징 처리가 된 sql문이다.
아래 코드를 보면 curPage에 따라 SELECT해오는 게시글 리스트가 다르다.

StringBuffer sql = new StringBuffer();
sql.append(" WITH temp AS( ");
sql.append(" SELECT ROWNUM AS no, temp.* ");
sql.append(" FROM ");
sql.append(" ( ");
sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
sql.append("    FROM tbl_board ");
sql.append("    ORDER BY seq desc ");
sql.append("    )temp ");
sql.append(" ) ");
sql.append(" SELECT temp.* FROM temp ");
sql.append(" WHERE temp.no BETWEEN ? AND ? ");

int start = (curPage - 1)* pageSize + 1 ;
int end = curPage * pageSize;
...
...
pstmt.setInt(1,  start);
pstmt.setInt(2,  end);

JDBC할 때 사용했던 코드를 그대로 가져왔기 때문에….

https://kouzie.github.io/jdbc/JDBC.-4일차/#페이징-처리

페이징 처리를 위해 필요한 값들이 있다.

  1. 한 화면에 출력되는 게시글 개수
  2. 한 화면에 출력할 페이지 블럭 수

이 두 가지만 알면 페이징에 필요한 나머지 값은 계산을 통해 얻어올 수 있다.

먼저 원할하게 페이징 처리를 위한 DTO클래스(PageBlock.java)를 하나 만들자.

/* PageBlock.java */
public class PageBlock {
  private int curPage = 1; //현재페이지
	private int numberOfBlock = 10; //출력할 페이지 블록 수
	private int numberOfBlocks = 0; //총 페이지 블록 수
	private int numberPerPage = 15; //출력할 게시글 수
	private int start = 1; //시작 페이지 블록 값
  private int end = start + numberOfBlock; //끝 페이지 블록 값
  public boolean prev, next; //이전, 다음버튼
  
  getCurPage(){...}
  setCurPage(){...}
  getNumberOfBlock(){...}
  setNumberOfBlock(){...}
  ...
  ...

	public PageBlock getNumberOfPages() {
		StringBuffer sql = new StringBuffer();
		sql.append(" SELECT CEIL(COUNT(*) / ?) numberOfBlocks FROM tbl_board ");
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			conn = DBConn.getConnection();
			pstmt = conn.prepareStatement(sql.toString());
			pstmt.setInt(1,  numberPerPage);
			rs = pstmt.executeQuery();
			if(rs.next())
			{
				numberOfBlocks = rs.getInt("numberOfBlocks");
			}
			rs.close();
			pstmt.close();
		}
		catch (Exception e) {
			System.out.println(e);
		}
	}
}

위와 같이 기본값을 세팅해놓자.

getNumberOfPages()메서드에선 모든 게시글 수 / 한페이지 출력 게시글 수 나누기 연산을 통해 페이지 블럭 수를 얻는다.

그리고 List.java에선 PageBlock DTO객체의 총 페이지 블럭 수 를 통해 다음 데이터를 얻어야 한다.

  • <이전페이지 이동, >이후 페이지 이동 아이콘을 띄울 것인지 띄우지 않을 것인지.

  • curPage에 따른 시작 페이지블록, 마지막 페이지 블록이 뭐가 되는지.

시작 페이지블록과 마지막 페이지 블록은 다음과 같이 구할 수 있다.

int pageBlockStart = (curPage-1)/numberOfBlock * numberOfBlock + 1;
int pageBlockEnd = (curPage-1)/numberOfBlock * numberOfBlock + numberOfBlock;

pageBlock.setStart(pageBlockStart);
pageBlock.setEnd(pageBlockEnd);

시작페이지가 1값일 경우 <아이콘을 출력할 필요 없고,
마지막 페이지가 pageBlockEnd일 때 >아이콘을 출력할 필요 없다.

pageBlock.prev = pageBlock.getStart() == 1 ? false : true;
pageBlock.next = pageBlock.getEnd() == pageBlock.getNumberOfBlocks() ? false : true;
request.setAttribute("pageBlock", pageBlock);

아이콘 출력 여부를 결정하는 pageBlock.prev, pageBlock.next을 초기화!

request.setAttributelist.jsp에게 보내기만 하면 된다.

이제 list.jsp기존 테이블에 tfoot태그를 추가해서 페이징 처리한 것을 출력하기만 하면 된다.

<!-- list.jsp -->
<tfoot>
<tr>
  <td colspan="5" align="center">
    <div class="pagination">
      <c:if test="${pageBlock.prev }">
        <a href="/jspPro/board/list?currentPage=${pageBlock.start - 1 }">&laquo;</a>
      </c:if>
      <c:forEach begin="${pageBlock.start }" end="${pageBlock.end }" step="1" var="i">
      
        <c:if test="${i eq pageBlock.curPage }">
          <a class="active" href="#">${i }</a>
        </c:if>
        
        <c:if test="${i ne pageBlock.curPage }"> <!-- not equal의 약자 -->
          <a href="/jspPro/board/list?currentPage=${i }">${i }</a>
        </c:if>
      </c:forEach>
      <c:if test="${pageBlock.next }">
        <a href="/jspPro/board/list?" class="subjectLink">&raquo;</a>
      </c:if>
    </div>
  </td>
</tr>
<tr>
  <td colspan="5" align="center">
    <a href="list" style="float: left;"></a>
    <a href="regist">글쓰기</a>
  </td>
</tr>
</tfoot>

image12

포문을 돌며 각 숫자에 ?currentPage=${i} 파라미터를 추가시킨다.

<<, >> 아이콘에는 각각 ?currentPage=${pageBlock.start - 1}, ?currentPage=${pageBlock.end + 1} 파라미터를 추가시킨다.

상태관리를 위해 계속 페이지 블럭 이동뿐 아니라 content.jsp로 이동할 때에도 currentPage를 파라미터로 달고가야 한다.
상당히 귀찮은게 content.jsp에서 list.jsp, delete.jsp, update.jsp등 이동할 때에도 currentPage를 유지하기 위해 파라미터로 달고 가야한다.
빨리 쿠키를 사용한 상태관리를 배워야…

게시글 검색하기

게시글 검색을 하려면 지금까지 작성했던 sql 대대적인 수정이 필요하다.

우선 list.jsptfoot태그에 수정을 위한 input태그를 삽입하자

페이지 처리 바로 아래에 있는 tr태그이다.

<tr>
  <td colspan="5" align="center">
    <form action="" method="get">
      <select name="searchCondition" id="searchCondition">
        <option value="1">제목</option>
        <option value="2">내용</option>
        <option value="3">글쓴이</option>
        <option value="4">제목+내용</option>
      </select>
      <input type="text" name="searchWord" id="searchWord" value="${param.searchWord}"/>
      <input type="submit" value="검색"/>
    </form>
  </td>
</tr>

image13

검색을 누르면 get방식으로 submit되어 List.javadoGet()메서드를 호출한다.

post방식으로 넘겨 doPost()메서드를 호출해도 되지만 doGet()에서 전체출력, 검색출력을 같은쿼리로 조금만 다르게 설정하면 모두 처리 가능하기 때문에 doGet()으로 모두 처리하겠다!

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  System.out.println("List doGet() called....");
  String curPage_param = request.getParameter("currentPage");
  String searchCondition = request.getParameter("searchCondition");
  String searchWord = request.getParameter("searchWord");

  if(searchCondition == null || searchCondition.isEmpty())
    searchCondition = "1";
  if(searchWord == null || searchWord.isEmpty())
    searchWord = "*";
  int curPage;
  if(curPage_param == null || curPage_param.isEmpty())
    curPage = 1;
  else
    curPage = Integer.parseInt(curPage_param);
  
  int pageSize = 15; //뿌릴 게시글 개수
  int numberOfBlock = 10; //표시할 페이지 수
  int start = (curPage - 1)* pageSize + 1 ;
  int end = curPage * pageSize;

  StringBuffer sql = new StringBuffer();
  sql.append(" WITH temp AS( ");
  sql.append(" SELECT ROWNUM AS no, temp.* ");
  sql.append(" FROM ");
  sql.append(" ( ");
  sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
  sql.append("    FROM tbl_board ");
  System.out.printf("searchCondition: %s\n", searchCondition);
  switch (Integer.parseInt(searchCondition)) {
  case 1: //제목
    sql.append("    WHERE REGEXP_LIKE(subject, ?, 'i') ");
    break;
  case 2: //내용
    sql.append("    WHERE REGEXP_LIKE(content, ?, 'i') ");
    break;
  case 3: //글쓴이
    sql.append("    WHERE REGEXP_LIKE(name, ?, 'i') ");
    break;
  case 4: //제목+내용
    sql.append("    WHERE REGEXP_LIKE(subject, ?, 'i') OR REGEXP_LIKE(content, ?, 'i') ");
    break;

  default:
    break;
  }
  sql.append("    ORDER BY seq desc ");
  sql.append("    )temp ");
  sql.append(" ) ");
  sql.append(" SELECT temp.* FROM temp ");
  sql.append(" WHERE temp.no BETWEEN ? AND ? ");

  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  ArrayList<MyBoardDTO> blist = null;
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    pstmt.setString(1,  searchWord);
    if(Integer.parseInt(searchCondition) == 4)
    {
      pstmt.setString(2,  searchWord);
      pstmt.setInt(3,  start);
      pstmt.setInt(4,  end);
    }
    else
    {
      pstmt.setInt(2,  start);
      pstmt.setInt(3,  end);
    }
    rs = pstmt.executeQuery();
    if(rs.next())
    {
      MyBoardDTO mdto = null;
      blist = new ArrayList<>();
      do {
        mdto = new MyBoardDTO();
        mdto.setNo(rs.getInt("no"));
        mdto.setSeq(rs.getInt("seq"));
        mdto.setName(rs.getString("name"));
        mdto.setEmail(rs.getString("email"));
        String subject = rs.getString("subject");
        if(!searchWord.equals("*"))
          subject = subject.replaceAll("(?i)"+searchWord, "<span class='searchWord'>"+searchWord+"</span>");
        mdto.setSubject(subject );
        mdto.setCnt(rs.getInt("cnt"));
        mdto.setRegDate(rs.getDate("regdate"));
        blist.add(mdto);
      } while (rs.next());
    }
    rs.close();
    pstmt.close();
  } catch (Exception e) {
    System.out.println(e);
  }

  PageBlock pageBlock = new PageBlock();
  pageBlock.setCurPage(curPage);
  pageBlock.setNumberPerPage(pageSize);
  pageBlock.setNumberOfBlock(numberOfBlock); //페이지 블록 10개씩 출력
  pageBlock.getSearchNumberOfPages(pageSize, Integer.parseInt(searchCondition), searchWord); 
  //바뀐 페이징 처리 함수
  int pageBlockStart = (curPage-1)/numberOfBlock * numberOfBlock + 1;
  int pageBlockEnd = (curPage-1)/numberOfBlock * numberOfBlock + numberOfBlock;
  if(pageBlockEnd > pageBlock.getNumberOfBlocks())
    pageBlockEnd = pageBlock.getNumberOfBlocks();
  pageBlock.setStart(pageBlockStart);
  pageBlock.setEnd(pageBlockEnd);
  pageBlock.prev = pageBlock.getStart() == 1 ? false : true; //start가 1이면 << 보일 필요 없음
  pageBlock.next = pageBlock.getEnd() == pageBlock.getNumberOfBlocks() ? false : true; //end가 pageBlock.numberOfBlock이면 >> 보일 필요 없음

  String path = "/days05/list.jsp";
  RequestDispatcher dipatcher = request.getRequestDispatcher(path);

  request.setAttribute("pageBlock", pageBlock);
  request.setAttribute("list", blist);
  dipatcher.forward(request, response);
}

검색기능이 추가되면서 귀찮은 작업들이 많이 늘었다….

우선 currentPage상태유지 했던 것 처럼 searchCondition, searchWord역시 파라미터로 계속 넘겨주며 유지시켜야 한다.

유지를 못시키는 순간 null예외가 List.java에서 발생하기 때문에 아래와 같이 간단히라도 예외처리를 해주어야 한다.

if(searchWord == null || searchWord.isEmpty())
  searchWord = "*";
if(searchCondition == null || searchCondition.isEmpty())
  searchCondition = "1";
int curPage;
if(curPage_param == null || curPage_param.isEmpty())
  curPage = 1;
else
  curPage = Integer.parseInt(curPage_param);

검색어를 입력하지 않아도 모든 게시물이 검색되어야 하는데 REGEXP_LIKE(subject, '*', 'i') 이런 형식으로 사용하면 된다.

따라서 검색어가 입력되지 않았을 때 에는 REGEXP_LIKE의 두번째 파라미터로 *이 들어갈 수 있도록 설정해주자.
(*자체를 검색하려면 별도의 문자열 처리 작업이 필요…)

if(searchWord == null || searchWord.isEmpty())
searchWord = "*";

페이징 처리도 searchWord에 대한 게시글 개수를 가져오도록 SELECT쿼리를 변경해야 한다.

public void getSearchNumberOfPages(int pageSize, int searchCondition, String searchWord)  {
  StringBuffer sql = new StringBuffer();
  int numberOfPages = 0;
  sql.append(" SELECT CEIL(COUNT(*) / ?) numberOfBlocks FROM tbl_board ");
  switch (searchCondition) {
  case 1: //제목
    sql.append(" WHERE REGEXP_LIKE(subject, ?, 'i') ");
    break;
  case 2: //내용
    sql.append(" WHERE REGEXP_LIKE(content, ?, 'i') ");
    break;
  case 3: //글쓴이
    sql.append(" WHERE REGEXP_LIKE(name, ?, 'i') ");
    break;
  case 4: //제목+내용
    sql.append(" WHERE REGEXP_LIKE(subject, ?, 'i') OR REGEXP_LIKE(content, ?, 'i') ");
    break;

  default:
    break;
  }
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  try {
    conn = DBConn.getConnection();
    pstmt = conn.prepareStatement(sql.toString());
    pstmt.setInt(1, pageSize);
    pstmt.setString(2,  searchWord);
    if(searchCondition == 4)
    {
      pstmt.setString(3,  searchWord);
    }
    rs = pstmt.executeQuery();
    if(rs.next())
    {
      numberOfBlocks = rs.getInt("numberOfBlocks");
    }
    rs.close();
    pstmt.close();
  }catch (Exception e) {
    System.out.println(e);
  }
}

상태유지를 위해 content.jsp, list.jsp, delete.jsp, update.jsp 등 각 페이지에서 currentPage, searchCondition, searchWord 값을 파라미터로 넘겨주어야 한다.

List.java에서 예외처리를 했기 때문에 에러는 나지 않지만 정상적인 구동을 위해선 파라미터를 넘겨주어야 한다.

$(".subjectLink").attr( "href", function( i, val ) {
  return val + "&currentPage=${param.currentPage }&searchCondition=${param.searchCondition}&searchWord=${param.searchWord}";
})

jquery로 a태그의 href속성에 다음 파라미터를 붙이자!
페이지 이동시 상태유지해야 하는 모든 a태그에게 subjectLink라는 class명을 부여하면 된다.

delete.jsp, edit.jsp에서 post방식으로 각 서블릿 객체로 넘기고 난 이후
doPost()에서 리다이렉트할 때에도 상태유지해주도록 하자.

String currentPage = request.getParameter("currentPage");
String searchCondition = request.getParameter("searchCondition");
String searchWord = request.getParameter("searchWord");
String location = "/jspPro/board/content?seq="+ seq +
  "&update="+state+"&currentPage="+currentPage+
  "&searchCondition="+searchCondition+"&searchWord="+searchWord;
response.sendRedirect(location );

image14

전체 소스: https://github.com/Kouzie/Kouzie.github.io/tree/master/_posts/JSP/Board

카테고리:

업데이트: