두 번째 작업을 위해 board1 폴더를 복사해서 board2로 폴더 명을 바꾸어 준다.

Java와 xml 파일에서 board1로 명명된 명칭을 board2로 바꾸어 진행한다.

첫 글자가 대문자로 Board1으로 되어 있는 것은 Board2로 바꾸어야 하니 주의해서 변경하기 바란다.

개인적으로 모든 파일의 "oard1"을 "oard2"로 바꾸기를 실행하여 처리했다.

샘플을 다운 받아 사용해도 되며 설치가 제대로 되었다면 http://localhost:8080/board/board2List으로 실행해 볼 수 있다.


이 단계를 진행하기 위해 조회수(BRDHIT), 삭제여부(BRDDELETEFLAG) 필드를 추가하고 기존 데이터를 위해 값을 설정해줘야 한다.

다음 SQL문을 실행해야 한다.

ALTER TABLE TBL_BOARD ADD (BRDHIT INT, BRDDELETEFLAG CHAR(1));

UPDATE TBL_BOARD SET BRDHIT=0, BRDDELETEFLAG='N' WHERE BRDNO IS NOT NULL;


테이블과 같이 boardVO 클래스도 수정해 준다.

package gu.board2;

public class boardVO {

    private String brdno, brdtitle, brdwriter, brdmemo, brddate, brdhit, brddeleteflag;

    ~~ 생략 ~~

    public String getBrdhit() {
        return brdhit;
    }

    public void setBrdhit(String brdhit) {
        this.brdhit = brdhit;
    }

    public String getBrddeleteflag() {
        return brddeleteflag;
    }

    public void setBrddeleteflag(String brddeleteflag) {
        this.brddeleteflag = brddeleteflag;
    }
}




글쓰기와 수정은 기본적으로 동일하다.

차이가 있다면 사용자 입력 폼을 호출할 때,

글쓰기는 빈 입력 상자들만 출력하고,

글 수정은 지정된 글번호에 대한 데이터를 조회하여

입력상자들의 초기값으로 지정하여 출력한다.


저장 처리에 있어서는 SQL문만 다를 뿐 동일하다.

즉, 폼에서는 글번호 값이 있으면 데이터를 가져오는 서비스를 호출하고

없으면 그냥 JSP파일을 호출한다.

JSP에서는 값이 없으면 텍스트 박스에 빈 값이 들어가기 때문에 새 글을 입력하게 된다.

따라서 글쓰기 컨트롤(boardForm)에

글수정 컨트롤(boardUpdate) 내용을 복사해서 다음과 같이 넣어주면 된다.

두 컨트롤을 합쳐서 글 번호(brdno) 값이 있으면 해당 데이터를 불러와서 수정으로,

없으면(null)  그냥 입력 폼을 호출하게 된다.

public String boardForm(HttpServletRequest request, ModelMap modelMap) throws Exception {
        String brdno = request.getParameter("brdno");
        if (brdno!=null) {
            boardVO boardInfo = boardSvc.selectBoardOne(brdno);
             modelMap.addAttribute("boardInfo", boardInfo);
        }
       
        return "board2/boardForm";
}

board2Ctr.java

JSP 파일은 글 수정 파일(boardUpdate.jsp)의 내용을 글쓰기(boardUpdate.jsp)에 넣어준다.

글 수정과 관련된 컨트롤과 jsp 파일은 삭제 한다.


저장 컨트롤에서도 Insert와 Update를 글번호 값(brdno) 존재 여부에 따라

다음 코드와 같이 각각 필요한 것을 호출해 준다.

public String boardSave(@ModelAttribute boardVO boardInfo) throws Exception {
       
        if (boardInfo.getBrdno()==null)
               boardSvc.insertBoard(boardInfo);
        else boardSvc.updateBoard(boardInfo);

        return "redirect:/board2List";
}

board2Ctr.java

여기에서는 두 개의 서비스를 호출했는데

if문을 서비스(board2Svc.java)에 넣어서 처리하는 것이 보다 효율적일 수 있다.

하나의 서비스에서 다른 2개의 SQL문을 상황에 따라 호출하는 것이 더 좋을 것이다.

직접 해보길 바란다.


다음과 같이 boardForm.jsp의 폼(form) 테그 method를 Post로 지정하는 것이 좋다.

<form name="form1" action="board2Save" method="post">

별도의 지정이 없는 경우 GET 이 기본(METHOD="GET")으로 지정된다.

GET 방식은 다음과 같이 URL 뒤에 변수이름과 값이 전송되는 형태이다.

http://localhost/board1/board1List?searchType=brdname&searchKeyword=홍

호출되는 URL의 물음표(?)뒤에 변수명과 값이 이퀄(=)로 구분되고, 

다수의 변수는 &로 구분되어 전송된다.

예에서는 searchType와 searchKeyword 두개의 변수가 사용되었다.


POST 방식은 지정된 방식에 의해 인코딩한 다음 서버로 전송되는 것으로

브라우저의 주소 입력란에 내용이 나타나지 않는다. 

일정한 크기 이상의 데이터를 전송할 때, 

GET은 내용이 잘릴 수 있지만 POST 방식은 모두 전송되기 때문에 

사용자가 입력하는 게시물 내용이나 첨부 파일 같이 전송되는 데이터가 많을 경우 POST방식을 사용한다.




'Java > 게시판 2' 카테고리의 다른 글

1. 게시판 확장 - 준비  (0) 2016.03.28
3. 게시판 확장 - 조회수  (5) 2016.03.28
4. 게시판 확장 - 삭제에서 숨기기로  (0) 2016.03.28
5. 게시판 확장 - 페이징  (10) 2016.03.20

조회수(BRDHIT)는 사용자가 글을 읽은 회수를 의미하며,

구현은 글 읽기가 호출되면 해당 글의 조회수를 1씩 증가(BRDHIT = BRDHIT + 1)시켜서 구현한다.

그리고, 리스트에서 그 값을 볼 수 있게 해주면 된다.




먼저, 조회수를 증가 시켜주는 다음과 같은 SQL문과 서비스를 작성한다(board2Svc.java).

<update id="updateBoard2Read" parameterType="String">
        UPDATE TBL_BOARD
              SET BRDHIT = BRDHIT + 1
         WHERE BRDNO=#{brdno}
</update>

Board2.xml

public void updateBoard2Read(String param) throws Exception {
    sqlSession.update("updateBoard2Read", param);
}

board2Svc.java

이 서비스(SQL)를 아래와 같이 글 읽기 컨트롤에서 호출하면 된다.

@RequestMapping(value = "/board2Read")
public String boardSave(HttpServletRequest request, ModelMap modelMap) throws Exception {
    String brdno = request.getParameter("brdno");
       
    boardSvc.updateBoard2Read(brdno);
    boardVO boardInfo = boardSvc.selectBoardOne(brdno);
    modelMap.addAttribute("boardInfo", boardInfo);
       
    return "board2/boardRead";
}

board2Ctr.java

이렇게 하여 사용자가 글을 읽을 때 마다 조회수를 증가시키게 된다.

이렇게 하여 증가된 조회수는 리스트에 다음의 코드를 추가하여 볼 수 있다.

즉 SELECT문으로 조회수(BRDHIT) 값을 가져오고,

<select id="selectBoard2List" resultType="gu.board2.boardVO" parameterType="gu.common.PageVO">
    SELECT BRDNO, BRDTITLE, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE, BRDHIT
      FROM TBL_BOARD
     WHERE BRDDELETEFLAG='N'
     ORDER BY BRDNO DESC
</select>

board2.xml

사용자에게 보여 주는 JSP에서 다음과 같이 출력하면 된다.

<thead>

                                <tr>

                                          <th>번호</th>

                                          <th>제목</th>

                                          <th>등록자</th>

                                          <th>등록일</th>

                                          <th>조회수</th>

                                </tr>

                     </thead>

                     <tbody>

                                <c:forEach var="listview" items="${listview}" varStatus="status">      

                                          <c:url var="link" value="board2Read">

                                                     <c:param name="brdno" value="${listview.brdno}" />

                                          </c:url>           

                                          <tr>

                                                     <td><c:out value="${listview.brdno}"/></td>

                                                     <td><a href="${link}"><c:out value="${listview.brdtitle}"/></a></td>

                                                     <td><c:out value="${listview.brdwriter}"/></td>

                                                     <td><c:out value="${listview.brddate}"/></td>

                                                     <td><c:out value="${listview.brdhit}"/></td>

                                          </tr>

                                </c:forEach>

                     </tbody>

boardList.jsp

이상의 과정으로 조회수 기능 추가는 끝인데, 이렇게만 두면 오작동할 수 있다.

다음과 같이 글 작성(Inset)시 추가한 두 개의 필드에 대한 값을 처리해 줘야 한다.

즉, 처음 쓰는 글이니 조회수는 0, 삭제되지 않았으니 N(no)을 넣어 준다.

테이블 생성시 Default 값으로 처리하는 것이 편하지만,

개인적으로 직관적인 것을 선호해서 Default처리를 잘 하지 않는다.

<insert id="insertBoard2" parameterType="gu.board2.boardVO" >
        INSERT INTO TBL_BOARD(BRDTITLE, BRDWRITER, BRDMEMO, BRDDATE, BRDHIT, BRDDELETEFLAG)
    VALUES (#{brdtitle}, #{brdwriter}, #{brdmemo}, NOW(), 0, 'N' )
</insert>

좀 더 나은 조회수 처리는 중복 데이터 방지일 것이다.

즉, 동일한 사람이 두 번 이상 읽은 경우 한번으로 처리하는 것이다.

별도의 테이블을 이용하여 해당 글을 읽은 사람의 이름(ID)를 저장하면 된다.

이 기능은 사용자 ID가 필요한데

본 예제에서는 사용자 관련 기능을 다루지 않기 때문에 구현하지 않는다.

각자 구현해 본다면 실력 향상에 도움이 될 것이다.







데이터 베이스에 저장된 데이터는 삭제 하지 않는 것이 좋다.

차후에 발생하는 여러 가지 문제를 막기 위해 실제로는 삭제하지 않고 개발한다.

즉, 실제로는 삭제하지 않고 삭제한 것처럼 사용한다는 의미로,

삭제 여부를 확인하는 필드(BRDDELETEFLAG)를 추가하여

삭제이면 안 보이게 처리하는 방식을 사용한다.

구현 방법은 다음과 같이 Delete문을 update문으로 수정하면 된다.

즉 삭제 필드(BRDDELETEFLAG)가 N이면 사용, Y이면 삭제가 되는 것이다.

<delete id="deleteBoard2One" parameterType="String">
        UPDATE TBL_BOARD
              SET BRDDELETEFLAG='Y'
         WHERE BRDNO=#{brdno}
</delete>

Board2.xml

데이터를 이용하는 SQL문도 다음과 같이 수정해 줘야 한다.

리스트의 경우 데이터를 가져올 때

삭제 되지 않은 데이터(WHERE BRDDELETEFLAG='N')만 가져오도록 수정한다.

<select id="selectBoard2List" resultType="gu.board2.boardVO" >
        SELECT BRDNO, BRDTITLE, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE, BRDHIT
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
</select>

~~ 생략 ~~
   
<update id="updateBoard2" parameterType="gu.board2.boardVO">
        UPDATE TBL_BOARD
              SET BRDTITLE=#{brdtitle}
                   , BRDWRITER=#{brdwriter}
                   , BRDMEMO=#{brdmemo}
         WHERE BRDDELETEFLAG='N'
              AND BRDNO=#{brdno}
</update>
       
<select id="selectBoard2One" parameterType="String" resultType="gu.board2.boardVO">
        SELECT BRDNO, BRDTITLE, BRDWRITER, BRDMEMO, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE
           FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
              AND BRDNO=#{brdno}
</select>


Board2.xml

글수정과 글읽기에서도 BRDDELETEFLAG 조건을 사용해야 한다.

이상의 SQL문에서

사용자가 글을 수정하고 저장(updateBoard2) 할 때

조건절에 BRDDELETEFLAG을 사용하였다.

글 읽기에서 데이터를 가져오는 SQL(selectBoard2One)에도

조건절에서 BRDDELETEFLAG을 사용하였다.

간단하게 생각하면 리스트에서 안보이니

굳이 여기에서 처리할 필요가 없을 것 같지만

북마크 하고, 시간이 지난 뒤 다시 찾아올 수 있다.

즉 주소를 직접 입력해서 접근할 수 있고,

이때, 삭제된 데이터는 수정하거나 읽을 수 없게 해야 한다.

여기에서는 구현하지 않았지만

삭제된 데이터라는 메시지를 준다면 더욱 좋을 것이다.


'Java > 게시판 2' 카테고리의 다른 글

1. 게시판 확장 - 준비  (0) 2016.03.28
2. 게시판 확장 - 글쓰기와 수정을 하나로  (0) 2016.03.28
3. 게시판 확장 - 조회수  (5) 2016.03.28
5. 게시판 확장 - 페이징  (10) 2016.03.20

현재의 리스트는 전체 데이터를 출력한다.

이것이 실제 사용될 경우 많은 데이터로 인해 문제를 발생시킬 수 있다.

즉, 예제는 몇 건 내지 몇 십 건의 데이터를 처리하기 때문에 문제가 없지만

실제 사용될 경우 몇 만에서 몇 십만의 데이터가 저장되기 때문에 한 페이지 실행되는데 아주 오랜 시간이 걸리고

웹 브라우저가 실행할 때도 메모리 부족 등의 문제가 발생할 수 있다.

따라서, 필요한 데이터만 가져오는 방식으로 처리해야 한다.

데이터가 얼마가 저장되어 있든지 필요로 하는 개수(화면에 출력하고자 하는 개수)만큼 가져오면

데이터 양과 관계없이 일정한 속도를 유지하게 된다.

이러한 처리를 페이징(Paging) 처리하고 한다.

이 부분은 많은 개발자들이 자주 실수하는 부분이며 웹 사이트 성능 저하의 주범이기도 하다.


다음과 같이 전체 데이터 개수가 9개가 있고 웹 페이지에는 3개씩 보여 준다고 가정한다.

1           

2           

3           

4           

5           

6           

7           

8           

9          

전체 페이지 수는 9 / 3으로 3이다.

첫 페이지의 데이터는 레코드 번호가 1부터 3까지

두 번째 페이지의 데이터는 레코드 번호가 4부터 6까지

세 번째 페이지의 데이터는 레코드 번호가 7부터 9가 된다.

여기에서 공식이 나오게 된다.

현재 페이지에 보여줄 데이터의 시작 번호 = (현재 페이지 – 1) * 출력 개수 + 1
현재 페이지에 보여줄 데이터의 종료 번호 = 시작 번호 + 출력개수  - 1


위 공식에 실제 페이지를 대입해 보면 

1 페이지의 시작 페이지는 = (1-1)* 3 + 1 = 1, 종료 페이지는 1 + 3 – 1 = 3
2 페이지의 시작 페이지는 = (2-1)* 3 + 1 = 4, 종료 페이지는 4 + 3 – 1 = 6
3 페이지의 시작 페이지는 = (3-1)* 3 + 1 = 7, 종료 페이지는 7 + 3 – 1 = 9

따라서, 다음과 같이 SQL 문이 실행되도록 만들면 되는 것이다.

MariaDB의 Limit는 주어진 레코드 번호 값에 해당하는 데이터를 가지고 오고, Oracle이나 MS-SQL은 Row_number나 TOP등의 명령어가 제공된다.

SELECT BRDNO, BRDTITLE,  ~~ 생략 ~~
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
         ORDER BY BRDNO DESC
         LIMIT 1, 3

SELECT BRDNO, BRDTITLE,  ~~ 생략 ~~
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
         ORDER BY BRDNO DESC
         LIMIT 4, 6

SELECT BRDNO, BRDTITLE,  ~~ 생략 ~~
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
         ORDER BY BRDNO DESC
         LIMIT 7, 9

이렇게 데이터를 가지고 오고,

리스트의 하단에 전체 페이지 리스트를 보여 주어 사용자가 선택하도록 하면 된다.

페이지 리스트는 전체 데이터 수(9)를 구해서

보여주고자 하는 개수(3)로 나누어 전체 페이지 수(3)를 구하고

그 수만큼 반복해서 링크를 생성한다.


이상의 개념을 이해한 경우 직접 만들어서 사용하면 되고,

그렇지 못해도 문제는 없다.

Common 폴더에 PageVO 클래스에 만들어 두었기 때문에 가져다 사용하면 된다.

public class PageVO {
    private Integer displayRowCount=10;      // 출력할 데이터 개수
    private Integer rowStart, rowEnd;         // 시작행번호, 종료행 번호
    private Integer totPage, totRow=0,        // 전체 페이수, 전체 데이터 수
                         page, pageStart, pageEnd;    // 현재 페이지, 시작페이지, 종료페이지

    public void pageCalculate(Integer total) {
        getPage();
        totRow  = total;
        totPage    = (int) ( total / displayRowCount );
       
        if ( total % displayRowCount > 0 ) totPage++;

        pageStart = (page - (page - 1) % 10) ;
        pageEnd = pageStart + 9;
        if (pageEnd > totPage) pageEnd = totPage;
       
        rowStart = ((page - 1) * displayRowCount) + 1 ;
        rowEnd = rowStart + displayRowCount -1;
    }
~~ 생략 ~~

이 코드를 간단하게 설명하면 전체 데이터 개수(total)를 받아서 totRow에 보관한다.

전체 데이터수를 출력하고자 하는 개수(displayRowCount)로 나누어

전체 페이지(totPage)를 계산한다.

무조건 올림을 해야 정확한 페이지 수를 얻을 수 있다.

예로 데이터 수가 21 개 일 때 단순 나누기를 하면 2페이지가 나온다.

무조건 올림을 해야 정확한 3페이지를 얻을 수 있다.

그래서 나눈 후 나머지가 있으면 전체 페이지(totPage) 값을 1증가 시킨다.

시작 행 번호(rowStart)와 종료 행 번호(rowEnd)는 앞서 언급한 공식을 이용하여 생성하였다. 

시작페이지(pageStart), 종료페이지(pageEnd)는 설명하지 않는다.

다만, 데이터가 수천, 수 만 일 경우 페이지도 수백, 수천이 된다.

모든 페이지를 보여 줄 수 없기 때문에

페이지 리스트에 대한 페이징 처리를 하게 되고 이것을 의미한다.

원리는 같지만 이상의 것만으로 복잡해서 넘어간다.

차후 코드를 살펴 보기 바란다.


먼저, 컨트롤을 수정한다.

@RequestMapping(value = "/board2List")
public String boardList(PageVO pageVO, ModelMap modelMap) throws Exception {

        pageVO.pageCalculate( boardSvc.selectBoardCount() ); // startRow, endRow

        List<?> listview   = boardSvc.selectBoardList(pageVO);
       
        modelMap.addAttribute("listview", listview);
        modelMap.addAttribute("pageVO", pageVO);

        return "board2/boardList";
}

board2Ctr.java

board2List의 파라메터로 PageVO 클래스 값을 받아오는 것은

사용자가 선택한 페이지 값을 알기 위한 것이다.

값이 없는 경우(Null) 1 페이지로 처리한다.

앞서 언급한 데로 PageVO에 메소드로 pageCalculate가 정의 되어 있기 때문에

서비스에서 전체 데이터 개수(COUNT)를 구하는 SQL문을 실행한다.

<select id="selectBoard2Count" resultType="Integer" >
        SELECT COUNT(*)
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
</select>

Board2.xml

public Integer selectBoardCount() throws Exception {
    return sqlSession.selectOne("selectBoard2Count");
}

board2Svc.java

pageVO에서 시작 행 번호(rowStart)와 종료 행 번호(rowEnd)를 계산해서(pageCalculate) 가지고 있기 때문에,

selectBoardList를 호출 할때 파라메터로 넘겨준다.

이 값들은 다음과 같이 사용된다.

    <select id="selectBoard2List" resultType="gu.board2.boardVO" parameterType="gu.common.PageVO">
        SELECT BRDNO, BRDTITLE, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE, BRDHIT
          FROM TBL_BOARD
         WHERE BRDDELETEFLAG='N'
         ORDER BY BRDNO DESC
         LIMIT ${rowStart-1}, 10 (주의)
    </select>

board2.xml

public List<?> selectBoardList(PageVO param) throws Exception {
    return sqlSession.selectList("selectBoard2List", param);
}

board2Svc.java

이상의 처리로 페이징 처리는 끝났다.

10개 이상의 데이터를 넣고 실행해보면 10 개만 출력되는 것을 알 수 있다.



  주의: 앞서 필요한 데이터를 가져오는 부분에 대한 설명에 있어서

LIMIT 1, 3

~~

LIMIT 4, 6

~~

LIMIT 7, 9 는

첫 번째부터 세 번째, 네 번째부터 여섯 번째 등으로 설명하기 하기 위한 것이고

MariaDB(Mysql)의 LIMIT 문은

첫 번째부터 세 개, 네 번째부터 세 개로 사용해야 한다.

따라서

LIMIT 1, 3

~~

LIMIT 4, 3

~~

LIMIT 7, 3 으로 사용해야 하고

앞서의 Mybatis SQL문(board2.xml)에서는 그렇게 사용하고 있다.

Oracle이나 MS-SQL에서 Row_number를 사용할 경우에는

앞서의 설명대로 사용하여 LIMIT ${rowStart-1}, ${rowEnd}로 표현해서 사용한다.



이제 JSP에서 리스트 하단에 페이지 번호들이 나오게 처리해야 하다.

시작페이지(pageStart)부터 종료페이지(pageEnd)까지 반복하면서(forEach)하면 그 값을 링크를 걸어서 출력(out)하면 된다.

              </c:forEach>
        </tbody>
    </table>
<c:if test="${pageVO.totPage>1}">
<div class="paging">
    <c:forEach var="i" begin="${pageVO.pageStart}" end="${pageVO.pageEnd}" step="1">
        <c:url var="pageLink" value="board2List">
        <c:param name="page" value="${i}" />
        </c:url>                       
            <a href="${pageLink}"><c:out value="${i}"/></a>
    </c:forEach>
</div>
</c:if>  

boardList.jsp

이 코드에서 현재 페이지(paveVO.page)는 링크를 걸지 않게 만들면 더 좋을 것이니 직접 해 보길 바란다.


게시물들은 등록된 후 수정되고 삭제 된다.

삭제 된 경우 리스트에서 보여 주는 글 번호가 들쑥날쭉 보이면서 뭔가 문제가 있는 것처럼 보인다. 

아래의 계산식을 사용하면 깨끗하게 이어진 새로운 번호가 보이게 된다. 

다음 코드를 사용해 보고 계산식은 각자 확인해 보길 바란다.

<tr>
    <td>
        <c:out value="${pageVO.totRow-((pageVO.page-1)*pageVO.displayRowCount + status.index)}"/>                   
    </td>
    <td><a href="${link}"><c:out value="${listview.brdtitle}"/></a></td>
    <td><c:out value="${listview.brdwriter}"/></td>
    <td><c:out value="${listview.brddate}"/></td>
    <td><c:out value="${listview.brdhit}"/></td>
</tr>

boardList.jsp















+ Recent posts