[포트폴리오 페이지]_14단계_CRUD 게시판 구현_(검색 기능)

2022. 4. 26. 14:10[Spring]_/[Spring]_포트폴리오 페이지 만들기

728x90
반응형

[환경]

 

개발툴 : IntelliJ

DB : oracle

프레임워크 : spring , mybatis

사용 언어 : ES6, Java , Html5 , CSS


완성 화면]

제목 검색
작성자 검색


개발 목표 : 기본 게시판 그릴 때 로직 변경 -> 작성자 or 제목 항목 포함해서 query 발송, 

ROWNUM 활용하여, bno(table 의 key) 를 숨기고 가상의 number 목록 표시


아래의 포스팅에서 로직 변경함

https://yn971106.tistory.com/77

 

[포트폴리오 페이지]_9단계_CRUD 게시판 구현_(feat.목록 조회기능 구현)

[환경] 개발툴 : IntelliJ DB : oracle 프레임워크 : spring , mybatis ---- 개발 목표 : 오라클 디비를 이용해서 게시판 CRUD 중 R(read) 만들기 [완료 화면] [디렉토리] [설명] board에 관한 controller 와 VO..

yn971106.tistory.com


[ 메뉴 1 의 전체 소스코드 ]

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <style>
        .board {
            width: 100%;

        }
    </style>
</head>
<body>
<div id="content">
    <div id="contentwrap">
        <h2>CRUD</h2>
        <button class="regi">등록</button>
        <select id="selectview">
            <option value="10" selected="selected">10개씩 보기</option>
            <option value="15">15개씩 보기</option>
            <option value="20">20개씩 보기</option>
        </select>
        <select class="searchoption">
            <option value="titlename">제목</option>
            <option value="wirtername">작성자</option>
        </select>
        <input type="text" class="searchtext" value =""/>
        <button class="detailsearch">조회</button>
        <table border="1" class="board">
            <thead>
            <tr>
                <th>번호</th>
                <th>제목</th>
                <th>글쓴이</th>
                <th>작성일자</th>
                <th>조회수</th>
            </tr>
            </thead>
            <tbody id="table">
            </tbody>
            <!-- forEach 문은 리스트 객체 타입을 꺼낼때 많이 활용된다. -->
        </table>
        <span id="nav"></span>
    </div>
</div>
</body>
</html>
<script>
    $(document).ready(async function () {

        let regibtn = document.querySelector('.regi');
        let tbody = document.querySelector('#table');
        let viewcnt = document.querySelector('#selectview');
        let nav = document.querySelector('#nav');
        let $common = $commons.history

        let searchoption = document.querySelector('.searchoption');
        let searchtext = document.querySelector('.searchtext');
        let detailsearchbtn = document.querySelector('.detailsearch');




        searchtext.onkeyup = function(e){
            if(e.key === "Enter" || e.keyCode === 13 ){
                detailsearchbtn.click();
            }

        }

        viewcnt.onchange = function(){
            detailsearchbtn.click();
        }




        regibtn.onclick = function () {
            let url = "/regi/loader.do"
            let parentContent = document.querySelector('#contentwrap');
            let id = _commons().util.random();
            $common.sethistory(url,parentContent,id);
            let test = $common.gethistory(url);
            console.log(test);

            $('#content').load(url,function(){

            });

        }


        detailsearchbtn.onclick = function(){
            let wr;
            let ti;

            if(searchoption.value == "titlename" ){
                ti = searchtext.value;
                wr = "";
            }else{
                wr = searchtext.value;
                ti = "";
            }
            createBoard(1,wr,ti);
            createPagenation(wr,ti);
        }

        detailsearchbtn.click();

        async function createPagenation(wr,ti) {
            let cnt;

            while(nav.firstChild){
                nav.removeChild(nav.firstChild);
            }

            await fetch('/board/boardcount.do', {
                method: "POST",
                headers: {
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    writer : wr,
                    title : ti
                })
            }).then(res => res.json())
                .then(data => {
                    cnt = data;
                })

            //총 페이지 수
            let totalpagecnt = Math.ceil(cnt / viewcnt.value);
            //페이지 숫자 생성.
            for (let i = 1; i <= totalpagecnt; i++) {
                //html 생성하기.
                let node = document.createElement('a');
                const template = `
                <a class="navclick" href="javascript:void(0)">\${i}</a>
                `
                node.innerHTML = template;

                node.querySelector('a').onclick = function () {
                    createBoard(i,wr,ti);

                }
                nav.appendChild(node);
            }

        }

        //테이블 생성
        async function createBoard(nowpage,wr,ti) {


            while(tbody.firstChild){
                tbody.removeChild(tbody.firstChild);
            }

            await fetch('/board/search.do', {
                method: "POST",
                headers: {
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    view: viewcnt.value, // ~만큼 보기
                    pagenum: nowpage, // 페이지 숫자클릭넘버.
                    writer : wr,
                    title : ti
                })
            }).then(res => res.json())
                .then(data => {

                    for (let i = 0; i < data.length; i++) {

                        let node = document.createElement('tr');
                        const template = `
                                        <th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
                                        <th>\${data[i].title}</th>
                                        <th>\${data[i].writer}</th>
                                        <th>\${data[i].regdate}</th>
                                        <th>\${data[i].viewcnt}</th>
                                        `
                        node.innerHTML = template;
                        tbody.appendChild(node);

                        let boardlink = document.getElementsByClassName('boardlink');
                        boardlink[i].onclick =function(){
                            selectBoard(data[i].bno);
                        }
                    }

                });


            }

         

    });
</script>

 

1] 조회 버튼 클릭 이벤트

detailsearchbtn.onclick = function(){
    let wr;
    let ti;

    if(searchoption.value == "titlename" ){
        ti = searchtext.value;
        wr = "";
    }else{
        wr = searchtext.value;
        ti = "";
    }
    createBoard(1,wr,ti);
    createPagenation(wr,ti);
}

이 부분은 조회버튼을 클릭했을 때의 이벤트이다.

선택 박스에서 제목으로 검색할지, 작성자로 검색할지를 확인하고 그 값을 createBoard 함수, createPagenation에 각각 전달한다.

 

또한 이 함수는 곧 테이블을 만들고 페이징 처리도 해주는 함수이기 때문에 아래의 코드처럼 작성하고

searchtext.onkeyup = function(e){
    if(e.key === "Enter" || e.keyCode === 13 ){
        detailsearchbtn.click();
    }

}

viewcnt.onchange = function(){
    detailsearchbtn.click();
}

 detailsearchbtn.click();

이 함수를 호출 하는 것으로 초기값, 검색, 갱신의 기능시 조회버튼을 click() 하는 것으로 대체하였습니다.

 

2] 테이블 생성 함수

async function createBoard(nowpage,wr,ti) {


    while(tbody.firstChild){
        tbody.removeChild(tbody.firstChild);
    }

    await fetch('/board/search.do', {
        method: "POST",
        headers: {
            'content-type': 'application/json'
        },
        body: JSON.stringify({
            view: viewcnt.value, // ~만큼 보기
            pagenum: nowpage, // 페이지 숫자클릭넘버.
            writer : wr,
            title : ti
        })
    }).then(res => res.json())
        .then(data => {

            for (let i = 0; i < data.length; i++) {

                let node = document.createElement('tr');
                const template = `
                                <th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
                                <th>\${data[i].title}</th>
                                <th>\${data[i].writer}</th>
                                <th>\${data[i].regdate}</th>
                                <th>\${data[i].viewcnt}</th>
                                `
                node.innerHTML = template;
                tbody.appendChild(node);

                let boardlink = document.getElementsByClassName('boardlink');
                boardlink[i].onclick =function(){
                    selectBoard(data[i].bno);
                }
            }

        });


    }

 

2-1] 상세 설명

 

우선 생성시에는 이미 있는 테이블을 먼저 지우고 시작합니다.

tbody > 즉 column 명을 제외한 아래 값들을 removeChild 를 이용해서 지웁니다 

 

그리고 fetch 를 통해 통신을 하게 되고

지정한 url 매핑으로 json 방식 통신을 진행합니다.

전달 값은 

view -> 얼만큼 보려고 하는지

pagnum -> 현제 선택한 페이지 넘버

writer -> 작성자

title -> 제목

을 전달합니다.

 

그리고 전달받은 값은 list 구조로 오게 설정하였으며, 이를 length를 이용해 전부 for문을 태워서 반복합니다.

let node = document.createElement('tr');
const template = `
                <th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
                <th>\${data[i].title}</th>
                <th>\${data[i].writer}</th>
                <th>\${data[i].regdate}</th>
                <th>\${data[i].viewcnt}</th>
                `
node.innerHTML = template;
tbody.appendChild(node);

노드를 만들고,

그 노드 안의 html 구조인 template 를 만들어서 appendchild 를 이용하여 적용시킵니다.

template 안에 전달받은 내용을 기입하려면 \${ 데이터 } 로 적용이 가능합니다.

 

let boardlink = document.getElementsByClassName('boardlink');
boardlink[i].onclick =function(){
    selectBoard(data[i].bno);
}

한줄을 그리고나서 바로 a 태그에 이벤트를 부착합니다. 해당 이벤트는 selectBoard 함수에 선택된 bno 의 값을 전달합니다.

selectBoard 는 다음 시간에 할 조회 기능 구현시 설명합니다. 

 

2-2] Controller

@RequestMapping(value = "/board/search.do")
@ResponseBody
public String boardSearchPOST(@RequestBody Map<String, String> map) throws Exception{
    System.out.println(" board 조회시작= ");
    Integer viewcnt = Integer.parseInt(map.get("view"));
    Integer nowpage = Integer.parseInt(map.get("pagenum"));
    
    String title = map.get("title");
    System.out.println("title = " + title);
    String writer = map.get("writer");
    System.out.println("writer = " + writer);


    Integer start = viewcnt * (nowpage-1) +1;
    Integer last = start + viewcnt -1;

    System.out.println("start + last = " + start + last);

    List list = boardService.boardSearch(start,last,writer,title);


    System.out.println("list = " + list);
    System.out.println(" board 조회끝= ");
    String json = new Gson().toJson(list);
    System.out.println("json = " + json);
    return json;
}

받아온 Json 데이터를 key를 이용해 분할합니다.

그리고 페이징 처리를 위해 다음과 같은 로직으로 계산합니다

 

디비가 보여줄 첫 번째 순서 = 몇개씩 볼건지 전달받은 값 * ( 지금 내가 선택한 페이지 번호 -1 ) + 1

ex) 만약 10개씩 보기, 2번째 페이지 번호 클릭시에는 첫번째 순서는 10*1+1 즉 11 번째부터 보여준다는 의미입니다.

 

디비가 어디까지 보여줄건지 번호 = 디비가 보여줄 첫 번째 순서 + 얼만큼 볼건지 전달받은 값 - 1

ex) 만약 10개씩 보기, 2번째 페이지 번호 클릭시에는  11+10-1 즉 20번째 값 까지 보여주겠다는 의미입니다.

 

이렇게 2가지의 Integer 과 작성자, 제목 의 String 값 4개를 전달합니다.

 

※service serviceImpl 생략

2-3] BoardMapper

List<BoardVO> boardSearch(Integer start, Integer last, String writer,String title);

4가지 파라미터를 전달하고 VO에 지정한 List 배열의 형태로 리턴합니다.

 

2-4] Mapper.xml

<select id="boardSearch" resultType="com.yoon.model.BoardVO">
    select bno, title, writer, regdate, viewcnt, NUM
    from (select ROWNUM AS NUM, e.*
          from (select * from board where writer like '%'||#{writer}||'%' and title like '%'||#{title}||'%' order by REGDATE desc) e)
    where NUM between #{start} and #{last}
</select>

쿼리가 가장 중요한데 , 우선 bno, title, writer , regdate ,viewcnt ,NUM 이라는 6개의 컬럼을 반환합니다.

 

받아올 곳은 Oracle 에서 제공하는 가상의 ROWNUM 을 활용하여 해당 번호를 NUM 이라고 생성합니다.

이를 다시 board 라는 테이블에서 중첩질의를 하는데,

 

like 를 사용해서 전달받은 데이터와 비슷한지 비교해서 비슷한 값을 가져옵니다.

그리고 그 값들은 NUM 이라는 가상의 번호가 쭉 붙게 되고

 

그 중에서 Between을 사용해서 시작과 끝 사이의 값을 반환합니다.

db console 화면

그리고 BoardMapper 에서 VO 객체의 값 리스트로 받아온다고 하였으니

가상의 NUM 을 게시판에 보여줄 것이기 때문에 VO에 추가를 해줘야 합니다.

 

2-5] BoardVO]

package com.yoon.model;

import java.util.Date;

public class BoardVO {
    //Field
    private int bno;        //게시물번호
    private String title;    //게시물제목
    private String content;    //게시물내용
    private String writer;    //게시물작성자
    private String regdate;    //게시물작성일자
    private int viewcnt;    //게시물조회수

    //게시물 인덱스
    private int NUM;

    public int getNUM() {
        return NUM;
    }

    public void setNUM(int NUM) {
        this.NUM = NUM;
    }



    public int getBno() {
        return bno;
    }

    public void setBno(int bno) {
        this.bno = bno;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    public String getRegdate() {
        return regdate;
    }

    public void setRegdate(String regdate) {
        this.regdate = regdate;
    }

    public int getViewcnt() {
        return viewcnt;
    }

    public void setViewcnt(int viewcnt) {
        this.viewcnt = viewcnt;
    }

    @Override
    public String toString() {
        return "BoardVO{" +
                "bno=" + bno +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", writer='" + writer + '\'' +
                ", regdate='" + regdate + '\'' +
                ", viewcnt=" + viewcnt +
                ", NUM=" + NUM +
                '}';
    }
}

getter 와 setter , 그리고 toString 만 해주면 됩니다. (단축키 alt + insert)

 

검색시 검색한 값과 비슷한 유형의 데이터를 선택한 범위 내에서 전달받을수 있게 되었습니다.

이제는 페이징 처리를 하겠습니다.

 

3-1] CreatePagenation

async function createPagenation(wr,ti) {
    let cnt;

    while(nav.firstChild){
        nav.removeChild(nav.firstChild);
    }

    await fetch('/board/boardcount.do', {
        method: "POST",
        headers: {
            'content-type': 'application/json'
        },
        body: JSON.stringify({
            writer : wr,
            title : ti
        })
    }).then(res => res.json())
        .then(data => {
            cnt = data;
        })

    //총 페이지 수
    let totalpagecnt = Math.ceil(cnt / viewcnt.value);
    //페이지 숫자 생성.
    for (let i = 1; i <= totalpagecnt; i++) {
        //html 생성하기.
        let node = document.createElement('a');
        const template = `
        <a class="navclick" href="javascript:void(0)">\${i}</a>
        `
        node.innerHTML = template;

        node.querySelector('a').onclick = function () {
            createBoard(i,wr,ti);

        }
        nav.appendChild(node);
    }

}

board 에 그려주는 값 외에도 하단의 페이지 숫자를 얼만큼 할건지에 대한 값도 계산해야 합니다.

이를 하기 위해서는 검색되는 값들의 총량을 알아야 합니다 -> Count() 쿼리

검색의 총량이기 때문에 시작과 끝 지점을 전달할 필요가 없습니다.

 

제목과, 작성자의 value 값만 전달하여 쿼리를 돌렸을때의 총량을 전달받도록 하였고,

 

총 페이지 숫자를 표현해야하는 수 = 쿼리 조회시 나온 row의 총합 / 얼만큼 보여줄건지 전달받은 값

ex) 만약 test 라는 작성자의 글이 51개가 있고, 10개씩 보여줄 것이라면, 51/ 10 -> 5.1 입니다. 올림처리 하여 6이 되고

1 2 3 4 5 6 총 6개의 페이지가 필요합니다. 각각 10개씩 보여줄 것이고, 마지막 6에는 하나의 데이터만 있게 됩니다. 

 

이 총 페이지 수 만큼 for 문을 돌며 a 태그에 이벤트를 겁니다.

각각의 숫자는 a 태그이며 해당 태그를 클릭시 클릭한 페이지 수를 createBoard 함수에 전달함으로써 현제 클릭한 페이지 번호를 전달 할 수 있게 됩니다.

 

3-2] Controller

@RequestMapping("/board/boardcount.do")
@ResponseBody
public Integer searchcount(@RequestBody Map<String, String> map) throws Exception{
    System.out.println("검색 총 수 파악");
    String writer = map.get("writer");
    System.out.println("writer = " + writer);
    String title = map.get("title");
    System.out.println("title = " + title);

    Integer result = boardService.searchcnt(writer,title);
    System.out.println("result = " + result);
    return result;
}

마찬가지로 key 와 value를 구분하고 검색 조건만 전달합니다.

 

※service serviceImpl BoardMapper 생략

 

3-3] Mapper.xml

<select id="searchcnt" resultType="int">
    select count(0)
    from board
    where writer like '%'||#{writer}||'%' and title like '%'||#{title}||'%'
</select>

일반 조회와 비슷하지만, ROWNUM을 할 필요가 없이 총량만 count 하여 Int 값으로 반환합니다.

 

이렇게 반환된 INT를 앞서 설명한 방식대로 동작하여 페이징 처리가 완료되었습니다.

 

감사합니다.

다음에는 중간에 잠시 나온 selectBoard 즉 row 선택시 상세정보가 나오게 하는 페이지를 구현해보겠습니다.

728x90
반응형