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.java
의 doGet()
메서드를 호출하게 되고
doGet()
에선 DB에서 게시판 목록을 SELECT
쿼리로 읽어온 후 DTO
를 request
객체에 담아 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>
페이징 처리는 밑에서…
게시판 쓰기
게시판 아래의 글쓰기를 클릭하면
<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="작성 완료">
<input type="reset" value="다시 작성">
</td>
</tr>
</table>
</form>
여기서 주의할 것은 form
태그의 속성이다.
post
방식으로 전송하는데 action
속성이 비어있다!
<form name="registForm" method= "post" action="">
action
속성이 없다는 것은 자기 자신 url
에 post
하겠다는 뜻,
Regist.java
서블릿 클래스가 포워딩을 통해 regist.jsp
로 이동시켰기 때문에 현재 클라이언트의 주소는 a
태그로 이동했던 주소 localhost/jspPro/board/regist
그대로다.
위의 url
에 post
했기 때문에 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 );
}
입력이 완료되면 성공, 실패 결과인 state
를 url
뒤에 붙여 List.java
서블릿 클래스로 리다이렉트 시킨다.
List.java
는 doGet()
을 호출해서 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);
}
doGet()
과 doPost()
가 다른 역할을 하고
.java
로 코딩한 Servlet
클래스와 .jsp
로 코딩한 제스퍼 서블릿 클래스가 서로 다른 업무 분담되어 있는 것을 주의하자.
게시글 보기
위의 게시글 리스트를 띄울때 아래코딩을 통해 모든 게시글 제목에 링크를 달아 놓았다.
<a href="content?seq=${dto.seq }">${dto.subject }</a>
링크 주소를 보면 알겠지만 /jspPro/board/content/
url-mapping
을 가진 서블릿 클래스(Content.java
)를 만들어야 한다.
게시글 보기는 딱히 doGet
과 doPost
의 작업을 나눌 필요 없다.(그냥 조회수를 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>
이제 수정, 삭제기능을 구현하면 게시판의 기본적인 기능은 끝이다!
게시글 수정하기
<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.jsp
는 content.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="작성 완료">
<input type="button" onClick="location.href = '/jspPro/board/content?seq=${param.seq }'" value="이전으로">
</td>
</tr>
</table>
</form>
Edit.java
에서 포워딩을 통해 edit.jsp
로 이동했기 때문에 url
주소는 그대로다.
/localhost/jspPro/board/edit?seq=581
또한 form
태그의 action
속성이 비어있다는 것을 주의하자.
이상태에서 submit
하면 Edit.java
의 doPost()
메서드를 호출하게 된다.
/* 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);
}
});
게시글 삭제
삭제는 수정보다 더 간단하다.
비밀번호를 확인하고 DELETE
쿼리를 수행하면 된다.
삭제하기의 링크주소는 아래와 같다.
<a href="/jspPro/board/delete?seq=${dto.seq }">삭제하기</a>
/board/delete
url-mapping
된 Delete.java
서블릿 클래스가 필요하고
삭제를 위한 비밀번호 입력용 페이지인 delete.jsp
가 필요하다.
또한 처음엔 비밀번호 입력을 위해 doGet()
을 통해 delete.jsp
으로 포워딩을 통해 이동하고
입력한 비밀번호을 DB에서 비교하기 위해 doPost()
를 호출한다.
즉 Delete.java
서블릿 클래스에서도 doGet()
과 doPost()
기능이 나뉜다.
맨처음 a태그를 클릭하면 a태그를 통해 링크로 이동하기 때문에 Delete.java
의 doGet()
메서드가 호출된다.
/* 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="삭제">
<input type="button" onClick="location.href = '/jspPro/board/content?seq=${param.seq }'" value="취소">
</td>
</tr>
</table>
</form>
취소를 누르면 다시 Content.java
의 doGet()
메서드를 호출 함으로 content.jsp
페이지도 포워딩한다.
확인을 누르면 form
태그의 action
으로 post
하게 되는데 비어있음으로 현재 자신url
로 post요청 함으로 Delete.java
의 doPost()
를 호출한다.
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.jsp
에 delete
결과를 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일차/#페이징-처리
페이징 처리를 위해 필요한 값들이 있다.
- 한 화면에 출력되는 게시글 개수
- 한 화면에 출력할 페이지 블럭 수
이 두 가지만 알면 페이징에 필요한 나머지 값은 계산을 통해 얻어올 수 있다.
먼저 원할하게 페이징 처리를 위한 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.setAttribute
로 list.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 }">«</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">»</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>
포문을 돌며 각 숫자에 ?currentPage=${i}
파라미터를 추가시킨다.
<<
, >>
아이콘에는 각각 ?currentPage=${pageBlock.start - 1}
, ?currentPage=${pageBlock.end + 1}
파라미터를 추가시킨다.
상태관리를 위해 계속 페이지 블럭 이동뿐 아니라 content.jsp
로 이동할 때에도 currentPage
를 파라미터로 달고가야 한다.
상당히 귀찮은게 content.jsp
에서 list.jsp
, delete.jsp
, update.jsp
등 이동할 때에도 currentPage
를 유지하기 위해 파라미터로 달고 가야한다.
빨리 쿠키를 사용한 상태관리를 배워야…
게시글 검색하기
게시글 검색을 하려면 지금까지 작성했던 sql 대대적인 수정이 필요하다.
우선 list.jsp
의 tfoot
태그에 수정을 위한 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>
검색을 누르면 get
방식으로 submit
되어 List.java
의 doGet()
메서드를 호출한다.
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 + "¤tPage=${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+"¤tPage="+currentPage+
"&searchCondition="+searchCondition+"&searchWord="+searchWord;
response.sendRedirect(location );
전체 소스: https://github.com/Kouzie/Kouzie.github.io/tree/master/_posts/JSP/Board