게시글 기능 전편에서 회원 관련 기능(로그인, 로그아웃, 비밀번호 변경)을 구현하였고
이번엔 게시판 관련 기능(게시판 목록보기, 게시글 쓰기, 게시글 보기, 게시글 수정, 게시글 삭제) 를 구현해보자.
지금까지 게시판 구현은 2번이나 하였다.
https://kouzie.github.io/jsp/JSP-게시판/#
https://kouzie.github.io/jdbc/JDBC.-4일차/#
기존에 있던 소스를 사용해서 게시글 목록보기를 구현해보자.
게시글 DB 생성 먼저 게시글 DB를 생성해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 CREATE SEQUENCE seq_article21; CREATE TABLE article21 ( article_no NUMBER PRIMARY KEY , writer_id VARCHAR (50 ) NOT NULL , writer_name VARCHAR (50 ) NOT NULL , title VARCHAR (255 ) NOT NULL , content CLOB , regdate DATE DEFAULT SYSDATE, moddate DATE DEFAULT SYSDATE, read_cnt NUMBER DEFAULT 0 );
게시글 목록 보기 게시글 목록 보기도 .../article/list.do url을 요청하면 아래 그림처럼 handler를 통해 Article객체가 들어간 ArrayList와 페이징 처리를 위해 사용했던 PageBlock객체가 포함된 ArticlePage를 반환한다.
{: .shadow}
commandHandler.properties파일에 핸드러 추가
/article/list.do=board21.article.command.ListArticleHandler
게시글 관련 url패턴은 모두 앞에 /article/을 붙일 것이다.
ArticleDao - 게시글 SELECT 쿼리를 갖고있는 객체 먼저 sql문을 가지고 있는 ArticleDao를 정의하자.
https://kouzie.github.io/jsp/JSP-게시판/#게시글-검색하기
위 주소에서 사용했던 페이징 처리 기법 과 게시글 검색 SELECT 쿼리 그대로 사용해서 구현할 것이다.
사용자가 입력한 검색조건, 검색문자열, 페이지 위치를 파라미터로 가져올 것이기 때문에 매개변수로 Connection객체와 request를 받는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public class ArticleDao { public ArrayList<Article> getArticleList (Connection conn, HttpServletRequest request) throws SQLException { System.out.println("ListArticleService process" ); 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 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 article_no, writer_name, title, content, regdate, read_cnt " ); sql.append(" FROM article21 " ); System.out.printf("searchCondition: %s\n" , searchCondition); switch (Integer.parseInt(searchCondition)) { case 1 : sql.append(" WHERE REGEXP_LIKE(title, ?, 'i') " ); break ; case 2 : sql.append(" WHERE REGEXP_LIKE(content, ?, 'i') " ); break ; case 3 : sql.append(" WHERE REGEXP_LIKE(writer_name, ?, 'i') " ); break ; case 4 : sql.append(" WHERE REGEXP_LIKE(title, ?, 'i') OR REGEXP_LIKE(content, ?, 'i') " ); break ; default : break ; } sql.append(" ORDER BY article_no desc " ); sql.append(" )temp " ); sql.append(" ) " ); sql.append(" SELECT temp.* FROM temp " ); sql.append(" WHERE temp.no BETWEEN ? AND ? " ); PreparedStatement pstmt = null ; ResultSet rs = null ; ArrayList<Article> article_list = null ; try { 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()) { Article adto = null ; article_list = new ArrayList <>(); do { adto = new Article ( rs.getInt("article_no" ), new Writer ("#" , rs.getString("writer_name" )), "title" , "content" , rs.getDate("regdate" ), null , rs.getInt("read_cnt" ) ); String title = rs.getString("title" ); if (!searchWord.equals("*" )) title = title.replaceAll("(?i)" +searchWord, "<span class='searchWord'>" +searchWord+"</span>" ); adto.setTitle(title); article_list.add(adto); } while (rs.next()); } } finally { JdbcUtil.close(rs); JdbcUtil.close(pstmt); } return article_list; } ...
PageBlock객체를 반환하는 getPageBlock()메서드 역시 페이징 처리를 하려면 검색조건, 검색명, 몇페이지인지 알아야 한다.
따라서 Connection객체와 request객체를 매개변수로 받는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ... public PageBlock getPageBlock (Connection conn, HttpServletRequest request) throws SQLException, NamingException { int pageSize = 15 ; int numberOfBlock = 10 ; 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); PageBlock pageBlock = new PageBlock (); pageBlock.setCurPage(curPage); pageBlock.setNumberPerPage(pageSize); pageBlock.setNumberOfBlock(numberOfBlock); pageBlock.getSearchNumberOfPages(conn, 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 ; pageBlock.next = pageBlock.getEnd() == pageBlock.getNumberOfBlocks() ? false : true ; return pageBlock; } }
ListArticleHandler - /article/list.do 이벤트 처리 핸들러 ListArticleHandler와 ListArticleService를 같이 설명하겠다.
게시글 리스트를 가져오는 과정인 ListArticleDao만 복잡하지 Handler와 Service객체는 얻어온 ArrayList객체와 PageBlock객체를 listArticle.jsp 페이지로 넘기기만 하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 public class ListArticleHandler implements CommandHandler { private ListArticleService listService = new ListArticleService (); @Override public String process (HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("ListArticleHandler process" ); ArticlePage articlePage = listService.getArticlePage(request); request.setAttribute("articlePage" , articlePage); return "/listArticle.jsp" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ListArticleService { private ArticleDao articleDao = new ArticleDao (); public ArticlePage getArticlePage (HttpServletRequest request) { try (Connection conn = ConnectionProvider.getConncection()) { ArrayList<Article> article_list = articleDao.getArticleList(conn, request); PageBlock pageBlock = articleDao.getPageBlock(conn, request); return new ArticlePage (article_list, pageBlock); } catch (Exception e) { throw new RuntimeException (); } } }
request객체에 ArrayList, PageBlock을 따로 담아도 되지만 ArticlePage란 하나의 객체로 감싸서 전송한다.
ArticlePage도 필드로 ArrayList와 PageBlock을 가지고 있는 간단한 클래스이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ArticlePage { ArrayList<Article> article_list; PageBlock pageBlock; public ArticlePage (ArrayList<Article> article_list, PageBlock pageBlock) { this .article_list = article_list; this .pageBlock = pageBlock; } public ArrayList<Article> getArticle_list () { return article_list; } public PageBlock getPageBlock () { return pageBlock; } }
View역할을 하는 listArticle.jsp파일은 아래와 같다.
사실 listArticle.jsp도 전에 구현했던 것을 그대로 가져다 썼다.
https://kouzie.github.io/jsp/JSP-게시판/#게시판-리스트-페이징-처리
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 <%@ 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" > <title > 게시판 목록</title > </head > <body > <div align ="center" > <h2 > 목록보기</h2 > <table style ="border: solid 1px gray; width: 600px;" > <thead > <tr > <th > 번호</th > <th > 제목</th > <th > 작성자</th > <th > 등록일</th > <th > 조회</th > </tr > </thead > <tbody > <c:if test ="${empty articlePage.article_list }" > <tr align ="center" > <td colspan ="5" > 등록된 게시가 없습니다</td > </tr > </c:if > <c:if test ="${not empty articlePage.article_list }" > <c:forEach items ="${ articlePage.article_list }" var ="dto" > <tr align ="center" > <td > ${dto.article_no }</td > <td > <a href ="/jspPro/article/read.do?no=${dto.article_no }" class ="subjectLink" > ${dto.title }</a > </td > <td > ${dto.writer.name }</td > <td > ${dto.regdate }</td > <td > ${dto.read_cnt }</td > </tr > </c:forEach > </c:if > </tbody > <tfoot > <tr > <td colspan ="5" align ="center" > <div class ="pagination" > <c:if test ="${ articlePage.pageBlock.prev }" > <a href ="/jspPro/article/list.do ?currentPage=${articlePage.pageBlock.start - 1 } &searchCondition=${param.searchCondition} &searchWord=${param.searchWord}" >« </a > </c:if > <c:forEach begin ="${articlePage.pageBlock.start }" end ="${articlePage.pageBlock.end }" step ="1" var ="i" > <c:if test ="${i eq articlePage.pageBlock.curPage }" > <a class ="active" href ="#" > ${i }</a > </c:if > <c:if test ="${i ne articlePage.pageBlock.curPage }" > <a href ="/jspPro/article/list.do ?currentPage=${i } &searchCondition=${param.searchCondition} &searchWord=${param.searchWord}" > ${i }</a > </c:if > </c:forEach > <c:if test ="${articlePage.pageBlock.next }" > <a href ="/jspPro/article/list.do ?currentPage=${articlePage.pageBlock.end + 1 } &searchCondition=${param.searchCondition} &searchWord=${param.searchWord}" >» </a > </c:if > </div > </td > </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 ="검색" /> <%-- <input type ="hidden" name ="currentPage" value ="${param.currentPage }" /> --%> </form > </td > </tr > <tr > <td colspan ="5" align ="center" > <a href ="/jspPro/index.do" style ="float: left;" > 홈</a > <a href ="/jspPro/article/write.do" > 글쓰기</a > </td > </tr > </tfoot > </table > </div > <script > $("#searchCondition" ).val ("${ empty param.searchCondition ? 1 : param.searchCondition}" ); $(".subjectLink" ).attr ( "href" , function ( i, val ) { return val + "¤tPage=${param.currentPage }&searchCondition=${param.searchCondition}&searchWord=${param.searchWord}" ; }) </script > </body > </html >
테스트를 위해 테이블에 랜덤으로 게시글을 200개정도 삽입해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DECLARE BEGIN FOR i IN REVERSE 1 .. 200 LOOP INSERT INTO article21 (article_no, writer_id, writer_name, title, content) VALUES ( seq_article21.nextval , dbms_random.string('U' , 5 ) , dbms_random.string('A' , 5 ) , dbms_random.string('A' , dbms_random.value(10 ,20 )) , dbms_random.string('A' , dbms_random.value(10 ,20 )) ); commit ; END LOOP; END ;
{: .shadow}
정상적으로 페이징 처리까지 완성되었다!사실 예전에 해놓은것…
게시글 쓰기 기능 구현 위의 listArticle.jsp파일의 글쓰기 링크는 아래와 같다.
<a href="/jspPro/article/write.do">글쓰기</a>
/article/write.do url을 요청한다.
commandHandler.properties에 아래와 같이 핸들러 추가
/article/write.do=board21.article.command.WriteArticleHandler
그리고 게시글 쓰기는 로그인 해야 사용 요청 가능하도록 /article/write.do url패턴이 LoginCheckFilter에 걸리도록 web.xml에서 설정하자.
1 2 3 4 5 6 7 8 9 <filter > <filter-name > loginCheckFilter</filter-name > <filter-class > board21.filter.LoginCheckFilter</filter-class > </filter > <filter-mapping > <filter-name > loginCheckFilter</filter-name > <url-pattern > /changePwd.do</url-pattern > <url-pattern > /article/write.do</url-pattern > </filter-mapping >
이제 LoginCheckFilter에 걸리는 url패턴은 총 2개이다.
WrtieArticle 이벤트 처리과정을 살펴보자.
{: .shadow}
WriteArticleHandler역시 get방식, post방식에 따라 get방식의 경우 newArticleForm.jsp로 포워딩 시키고 post방식의 경우 WriteArticleService → AricleDao(sql)을 실행시키고 입력 결과를 출력하는 newArticleSuccess.jsp로 포워딩 시킨다.
먼저 입력 form이 있는 newArticleForm.jsp페이지와 성공화면인 newArticleSuccess.jsp페이지를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > 게시글 입력 성공</title > </head > <body > <h3 > newArticleSuccess.jsp</h3 > 게시글을 등록했습니다. <br > ${ctxPath = pageContext.request.contextPath ; ''} <a href ="${ctxPath}/article/list.do" > [게시글목록보기]</a > <a href ="${ctxPath}/article/read.do?no=${ newArticleNo }" > [게시글내용보기]</a > </body > </html >
{: .shadow}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <%@ page 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 > <h3 > newArticleForm.jsp</h3 > <form method ="post" > <p > 제목:<br > <input type ="text" name ="title" value ="${param.title}" > <c:if test ="${errors.title}" > 제목을 입력하세요.</c:if > </p > <p > 내용:<br > <textarea name ="content" rows ="5" cols ="30" > ${param.content}</textarea > </p > <input type ="submit" value ="새 글 등록" > </form > </body > </html >
{: .shadow}
WriteArticleHandler - /article/write.do 쓰기 이벤트 헨들러 너무많이 봐서 WriteHandler의 구조가 대충 예상이 간다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class WriteArticleHandler implements CommandHandler { private static final String FORM_VIEW = "/newArticleForm.jsp" ; private WriteArticleService writeService = new WriteArticleService (); @Override public String process (HttpServletRequest request, HttpServletResponse response) throws Exception { if (request.getMethod().equalsIgnoreCase("GET" )) { System.out.println("LoginHandler preocess GET" ); return processForm(request, response); } else if (request.getMethod().equalsIgnoreCase("POST" )) { System.out.println("LoginHandler preocess POST" ); return processSubmit(request, response); } else { response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return null ; } } private String processForm (HttpServletRequest request, HttpServletResponse response) { return FORM_VIEW; } private String processSubmit (HttpServletRequest request, HttpServletResponse response) throws IOException { Map<String, Boolean> errors = new HashMap <>(); User user = (User) request.getSession().getAttribute("authUser" ); WriteRequest writeReq = createWriteRequest(user, request); writeReq.validate(errors); request.setAttribute("errors" , errors); if (!errors.isEmpty()) { System.out.println("errors is not empty" ); System.out.println(errors.get("title" )); return FORM_VIEW; } int newArticleNo = writeService.write(writeReq); request.setAttribute("newArticleNo" , newArticleNo); return "/newArticleSuccess.jsp" ; } private WriteRequest createWriteRequest (User user, HttpServletRequest request) { Writer writer = new Writer (user.getId(), user.getName()); return new WriteRequest (writer, request.getParameter("title" ), request.getParameter("content" )); } }
processSubmit()메서드만 달라졌는데 특이한점은 회원가입 때 처럼 입력값을 검증하는 WriteRequest객체를 정의하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 public class WriteRequest { private Writer writer; private String title; private String content; ... ...(생성자, get, set메서드) public void validate (Map<String, Boolean> errors) { if (title == null || title.trim().isEmpty()) { errors.put("title" , Boolean.TRUE); } } }
validate()메서드를 보면 title값이 공백이거나 null이라면 errors객체에 엔트리를 삽입
form태그로부터 정상적으로 값이 넘어왔다면 WriteRequest객체를 DB에 INSERT하기위해 WriteArticleService의 write메서드로 넘긴다.
ArticleDao - insert메서드를 추가 게시글을 INSERT하기 위해 ArticleDao클레스에 insert메서드를 추가하자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class ArticleDao { public Article insert (Connection conn, Article article) throws SQLException { PreparedStatement pstmt = null ; Statement stmt = null ; ResultSet rs = null ; try { pstmt = conn.prepareStatement("INSERT INTO article21 " + "(article_no, writer_id, writer_name, title, content) " + "VALUES (seq_article21.nextval, ?, ?, ?, ?)" ); pstmt.setString(1 , article.getWriter().getId()); pstmt.setString(2 , article.getWriter().getName()); pstmt.setString(3 , article.getTitle()); pstmt.setString(4 , article.getContent()); int insertedCount = pstmt.executeUpdate(); if (insertedCount > 0 ) { stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT * FROM (SELECT * FROM article21 ORDER BY article_no desc) WHERE ROWNUM = 1" ); if (rs.next()) { return new Article ( rs.getInt("article_no" ), new Writer (rs.getString("writer_id" ), rs.getString("writer_name" )), rs.getString("title" ), rs.getString("content" ), rs.getDate("regdate" ), rs.getDate("moddate" ), rs.getInt("read_cnt" ) ); } } return null ; } finally { JdbcUtil.close(rs); JdbcUtil.close(stmt); JdbcUtil.close(pstmt); } } public PageBlock getPageBlock (Connection conn, HttpServletRequest request) throws SQLException, NamingException { ... } public ArrayList<Article> getArticleList (Connection conn, HttpServletRequest request) throws SQLException { ... }
ArticleDao객체엔 아직 추가할 메서드가 많다.
게시글 삭제, 수정, 조회, 조회수 증가 등 의 메서드가 더 추가될 예정이다.
WriteArticleService - insert메서드를 수행 ArticleDao에 추가한 insert메서드를 수행하는 서비스 객체를 정의하자.
게시글 추가작업에선 게시글 추가만 할 뿐 다른 부가적인 작업이 없기 때문에 트랜잭션으로 묶을 필요는 없겠지만 후의 유지보수를 생각해서 트랜잭션 처리를 해야한다.
insert와중에 예외가 발생하면 rollback시키도록 하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class WriteArticleService { private ArticleDao articleDao = new ArticleDao (); public int write (WriteRequest req) { Connection conn = null ; try { conn = ConnectionProvider.getConnection(); conn.setAutoCommit(false ); Article article = toArticle(req); Article savedArticle = articleDao.insert(conn, article); if (savedArticle == null ) { throw new RuntimeException ("faild to insert article" ); } conn.commit(); return savedArticle.getArticle_no(); } catch (SQLException | NamingException e) { JdbcUtil.rollback(conn); throw new RuntimeException (e); } finally { JdbcUtil.close(conn); } } private Article toArticle (WriteRequest req) { Date now = new Date (); Article article = new Article ( 0 , req.getWriter(), req.getTitle(), req.getContent(), now, now, 0 ); return article; } }
toArticle()메서드를 통해 request객체를 매개변수로 받아 insert()메서드를 위한 DTO객체를 생성한다.
게시글 쓰기 외에도 기타 클래스에도 이런 메서드를 정의해 두면 가독성을 높일 수 있다.
게시글 조회 기능 게시글 목록에서 게시글을 클릭하거나 게시글 쓰기 완료 후 newArticleSuccess.jsp페이지에서 [게시글내용보기] 링크를 클릭하면 게시글을 보여주어야 한다.
게시글 조회 이벤트 처리 구조는 간단하다.
get, post도 나뉘지 않으며 조회할 게시글이 없어 예외가 발생할경우 404에러를 reponse객체에 담아 반환하기만 하면 된다.
{: .shadow}
정상적으로 게시글을 찾았다면 Article객체에 담아 readArticle.jsp에서 출력하면 된다.
ReadArticleHandler - /article/read.do 조회 이벤트 헨들러 따라서 ReadArticleHandler정의도 몇줄 되지 않는다. 조회할 게시글 넘버만 파라미터로 전달받아 DB에서 SELECT한 뒤 View페이지 readArticle.jsp 로 이동하여 결과를 출력한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ReadArticleHandler implements CommandHandler { private ReadArticleService readService = new ReadArticleService (); @Override public String process (HttpServletRequest request, HttpServletResponse response) throws Exception { int articleNum = Integer.parseInt(request.getParameter("no" )); try { Article article = readService.getArticle(articleNum, true ); request.setAttribute("article" , article); return "/readArticle.jsp" ; } catch (ArticleNotFoundException e) { request.getServletContext().log("no article" , e); response.sendError(HttpServletResponse.SC_NOT_FOUND); return null ; } } }
ArticleDao - 게시글조회, 조회수 증가 메서드 추가 게시글 조회는 조회작업, 조회수 증가작업 2가지를 같이 수행해햐 한다.
ArticleDao에 selectById(), increaseReadCount()메서드를 정의하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class ArticleDao { public Article insert (Connection conn, Article article) throws SQLException { ... } public ArrayList<Article> getArticleList (Connection conn, HttpServletRequest request) throws SQLException { ... } public PageBlock getPageBlock (Connection conn, HttpServletRequest request) throws SQLException, NamingException { ... } public Article selectById (Connection conn, int no) throws SQLException { PreparedStatement pstmt = null ; ResultSet rs = null ; try { pstmt = conn.prepareStatement("SELECT * FROM article21 WHERE article_no = ?" ); pstmt.setInt(1 , no); rs = pstmt.executeQuery(); Article article = null ; if (rs.next()) { article = new Article ( rs.getInt("article_no" ) , new Writer (rs.getString("writer_id" ), rs.getString("writer_name" )) , rs.getString("title" ) , rs.getString("content" ) , rs.getDate("regdate" ) , rs.getDate("moddate" ) , rs.getInt("read_cnt" )); } return article; } finally { JdbcUtil.close(rs); JdbcUtil.close(pstmt); } } public void increaseReadCount (Connection conn, int no) throws SQLException { try (PreparedStatement pstmt = conn.prepareStatement( "UPDATE article21 SET read_cnt = read_cnt+1 WHERE article_no = ?" )) { pstmt.setInt(1 , no); pstmt.executeUpdate(); } } }
selectById()메서드는 매개변수로 조회할 게시글 no를 받고 결과값 Article객체를 반환한다.
ReadArticleService - 게시글 조회, 조회수 증가 메서드 호출 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ReadArticleService { private ArticleDao articleDao = new ArticleDao (); public Article getArticle (int articleNum, boolean increaseReadCount) throws SQLException { Connection conn = null ; try { conn = ConnectionProvider.getConnection(); conn.setAutoCommit(false ); if (increaseReadCount) { articleDao.increaseReadCount(conn, articleNum); } Article article = articleDao.selectById(conn, articleNum); if (article == null ) { throw new ArticleNotFoundException (); } conn.commit(); return article; } catch (NamingException | SQLException e) { conn.rollback(); throw new RuntimeException (); } finally { JdbcUtil.close(conn); } } }
조회수를 먼저 증가시키고 Article객체를 받아온다.
increaseReadCount(), selectById()중 하나라도 예외가 발생한다면 rollback하기 때문에 조회수가 증가 됬더라도 게시글 조회에서 실패했다면 조회수 증가는 무효된다.
사실 게시판 프로젝트에서 트랜잭션다운 트랜젝션은 이 작업밖에 없는것 같다….
최종적으로 ReadArticleService객체가 게시글을 조회해서 ReadArticleHandler에 반환하면 readArticle.jsp에 출력된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > JSP / Servelet Class - kouzie(2019. 5. 14.-오전 9:31:26)</title > <style > table , td , tr { border : solid 1px gray; } table { border-spacing : 3px ; border-collapse : separate; } </style > </head > <body > <div align ="center" > <table > <tr > <td > 이름</td > <td > ${ article.writer.name }</td > <td > 등록일 / 수정일</td > <td > ${ article.regdate }/ ${ article.moddate }</td > </tr > <tr > <td > ID</td > <td > ${ article.writer.id }</td > <td > 조회</td > <td > ${ article.read_cnt }</td > </tr > <tr > <td > 제목</td > <td colspan ="3" > ${article.title }</td > </tr > <tr > <td colspan ="4" style ="padding: 15px;" > ${article.content }</td > </tr > <tr > <td colspan ="4" align ="center" > <a href ="/jspPro/article/edit.do?seq=${article.article_no }" > 수정하기</a > <a href ="/jspPro/article/list.do?currentPage=${ param.currentPage } &searchCondition=${param.searchCondition} &searchWord=${param.searchWord}" > 목록으로</a > <a href ="/jspPro/article/delete.do?seq=${article.article_no }" > 삭제하기</a > </td > </tr > </table > </div > </body > </html >
{: .shadow}
게시글 삭제 기능 게시글 수정 기능