본문 바로가기
Spring/study

spring 21강 게시판 만들기2( 페이지 나누기, 검색 기능 )

by avvin 2019. 7. 4.

spring 21강 게시판 만들기2( 페이지 나누기, 검색 기능 )



게시판.sql

delete from board;


--게시물 1000개 입력


declare

i number := 1;


begin

while i<= 1000 loop

insert into board (bno, title, content, writer)

values

( (select nvl( max(bno) +1, 1 ) from board ), '제목' || i , '내용' || i , 'kim' ); -- id는 member테이블에 있는 id로 입력해야함

i := i+1;

end loop;

end;

/


*|| : 오라클에서는 + 연산자



페이지 나누는 쿼리

select * from(

select rownum as rn, A.* from ( // 괄호 안 A테이블에 다시 rownum을 붙인다. (A테이블에서는 rownum왜 붙이는거지)


select rownum, bno, title, content, b.regdate, viewcnt, usename  -- (3)

from board b, tbl_member m  -- (1)

where b.writer = m.userid -- (2)

order by bno desc, regdate desc  -- (마지막)


) A

) where rn between 41 and 50;  // 10페이지씩 불러오기  




rownum은 sql을 실행한 후에 번호를 붙임


service.board/ Pager.java


페이지 나누기


- 페이지당 게시물수 : 10개

- 전체 게시물수 : 991개

- 몇 페이지? : 100

991 / 10 => 99.1 올림 => 100페이지



- 페이지의 시작번호, 끝번호


시작 번호( (현재페이지 -1) * 페이지당 게시물 수(10) + 1 ) //pageBegin

 ..

 ...

 ..

 ...

 10

 끝 번호 ( 시작번호 + 페이지당 게시물 수 -1 )//pageEnd


where rn between 1 and 10


1페이지 => 1 ~ 10

2페이지 => 11 ~ 20

 ...


시작 글번호 = (현재페이지 -1) * 페이지당 게시물 수(10) + 1

끝 글번호 = 시작번호 + 페이지당 게시물 수 -1


ex) 현재 6페이지 => 50+1 = 51번이 시작번호

     51+10-1 = 60번이 끝번호



[1]   [2]   [3]   [4]   [5]   [6]   [7]    [8]    [9]    [10]

 ▲페이지 블록 하나



-전체 페이지 블록 □ 수  


전체페이지 갯수 ( 91( [ n ] ) ) / 10 (소수점 이하 올림) => 10


[1]   [2]   [3]   [4]   [5]   [6]   [7]    [8]    [9]    [10]

× 10   




-현재 페이지가 속한 블록


(현재 페이지 -1) / 페이지 블록 단위 + 1



-페이지 블록의 시작 번호


 ( 현재 블록 - 1 ) *10 +1


service / Pager.java


 PAGE_SCALE

 페이지당 게시물 수

 

BLOCK_SCALE 

 화면당 페이지 수

 

curPage 

현재 페이지 

  ( 글번호 - 1 ) / PAGE_SCALE + 1

 prevPage

이전 페이지 

 [이전]을 눌렀을 때 이동할 페이지 번호

 nextPage

다음 페이지 

 [다음]을 눌렀을 때 이동할 페이지 번호

totPage 

 전체 페이지 개수

  글 개수 / PAGE_SCALE + ( 글 개수 % PAGE_SCALE ==0 ? 0 : 1 )

totBlock

전체 페이지 블록 개수

  totPage / BLOCK_SCALE => 소수점 이하 올림  

curBlock 

현재 페이지 블록 

  현재 페이지 - 1 / BLOCK_SCALE + 1

 prevBlock

이전 페이지 블록 

  

 nextBlock

다음 페이지 블록 

 // where rn between #{start} and #{end}

 pageBegin

#{start} 

 

 pageEnd

#{end} 

 

 blcokBegin

현재블록의 시작번호 

 

 blockEnd

현재 블록 끝번호 

 



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
package com.example.spring02.service.board;
 
public class Pager {
    
    public static final int PAGE_SCALE=10//페이지당 게시물수
    public static final int BLOCK_SCALE=10;//화면당 페이지수
    
    private int curPage; //현재 페이지
    private int prevPage;//이전 페이지
    private int nextPage;//다음 페이지
    private int totPage;//전체 페이지 갯수
    private int totBlock; //전체 페이지블록 갯수
    private int curBlock; //현재 블록
    private int prevBlock; //이전 블록
    private int nextBlock; //다음 블록
    private int pageBegin; // #{start} 변수에 전달될 값
    private int pageEnd; // #{end} 변수에 전달될 값
    private int blockBegin; //블록의 시작페이지 번호
    private int blockEnd; //블록의 끝페이지 번호
    
    //생성자
    // Pager(레코드갯수, 출력할페이지번호)
    public Pager(int count, int curPage) {
        curBlock = 1//현재블록 번호
        this.curPage = curPage; //현재 페이지 번호
        setTotPage(count); //전체 페이지 갯수 계산
        setPageRange(); // #{start}, #{end} 값 계산
        setTotBlock(); // 전체 블록 갯수 계산
        setBlockRange()//블록의 시작,끝 번호 계산
    }
    
    //블록의 시작, 끝 번호 계산 ( (현재페이지-1) /10 +1 )
    public void setBlockRange() {
        //원하는 페이지가 몇번째 블록에 속하는지 계산
        curBlock=(curPage-1)/BLOCK_SCALE + 1;
        //블록의 시작페이지,끝페이지 번호 계산
        blockBegin=(curBlock-1)*BLOCK_SCALE+1;
        blockEnd=blockBegin+BLOCK_SCALE-1;
        //마지막 블록 번호가 범위를 초과하지 않도록 처리
        if(blockEnd > totPage) {
            blockEnd = totPage;
        }
        //[이전][다음]을 눌렀을 때 이동할 페이지 번호
        //첫번째 페이지블록이면 1, 아니면 전 블록 마지막 페이지
        prevPage=(curBlock==1) ? 1 : (curBlock-1)*BLOCK_SCALE;
        
        //현재 블록이 총블록 수보다 작거나 같으면  (현재블록*10 +1)
        //= 다음블록 첫페이지
        //?? 현재블록이 총블록보다 큰 경우는 어떤 경우?
        nextPage=curBlock>totBlock ? (curBlock*BLOCK_SCALE)
                : (curBlock*BLOCK_SCALE)+1;
        //마지막 페이지가 범위를 초과하지 않도록 처리
        //다음페이지가 마지막페이지보다 크거나 같을 경우 다음페이지 = 마지막페이지
        if(nextPage >= totPage) {
            nextPage=totPage;
        }
    }
 
    
    //블록의 갯수 계산
    public void setTotBlock() {
        totBlock = (int)Math.ceil(totPage*1.0 / BLOCK_SCALE);
    }
    
// where rn between #{start} and #{end}에 입력될 값        
    public void setPageRange() {
// 시작번호=(현재페이지-1)x페이지당 게시물수 + 1
// 끝번호=시작번호 + 페이지당 게시물수 - 1        
        pageBegin = (curPage-1* PAGE_SCALE + 1;
        pageEnd = pageBegin + PAGE_SCALE - 1;
    }
    
 
...GETTER / SETTER 코드 생략 ...
 
    //전체 페이지 갯수 계산
    public void setTotPage(int count) {
        // Math.ceil() 올림        
        totPage = (int)Math.ceil(count*1.0 / PAGE_SCALE);
    }
 
    
    
}




BoardController.java의 list.do 수정


레코드 개수 구하기 > 페이징 객체로 시작 번호, 끝 번호 구하기 > BoardService listAll에 전달


( list.jsp의 list(page) 함수에서 넘어온 )curPage를 GET방식 파라미터로 받아오고  defaultValue=1 로 설정해준다.


search_option과 keyword는 POST방식의 폼데이터로 넘어오지만 

개별값으로 받아서 쓰는것이 편하므로 @RequestParam으로 받는다.

search_option은 defaultValue="all" / keyword는 defaultValue="" 빈값으로 설정  


서비스에 검색옵션과 키워드를 주고 레코드 개수를 세도록 처리를 넘기고 그 값을 count 변수에 담는다.


페이징 처리하는 객체 Pager의 생성자에 레코드 개수, 현재 페이지를 주어 페이징처리 객체를 생성한다.


pager에서 시작 번호/ 끝번호를 받아온다


서비스의 listAll ( 검색옵션, 키워드, 시작번호, 끝번호 )로 데이터를 받아와


(이 시작번호/끝번호가 Mapper의  where rn between #{start} and #{end}  에 들어간다 ) 


BoardDAO타입 List에 처리한 데이터를 담아준다.  


>> map에 리스트, 글개수, 페이징 객체, 검색 옵션, 키워드를 담고 mav에 담아 포워딩


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
    @RequestMapping("list.do"//세부적인 url pattern
    public ModelAndView list(
@RequestParam(defaultValue="name"String search_option,
@RequestParam(defaultValue=""String keyword,
@RequestParam(defaultValue="1") int curPage
                        throws Exception{
        //레코드 갯수 계산
        int count=
                boardService.countArticle(search_option,keyword);
        //페이지 관련 설정
        Pager pager=new Pager(count, curPage);
        int start=pager.getPageBegin();
        int end=pager.getPageEnd();
        
        List<BoardDTO> list=
boardService.listAll(search_option,keyword,start,end); //게시물 목록
        ModelAndView mav=new ModelAndView();
        HashMap<String,Object> map=new HashMap<>();
        map.put("list", list); //map에 자료 저장
        map.put("count", count);
        map.put("pager", pager); //페이지 네비게이션을 위한 변수
        map.put("search_option", search_option);
        map.put("keyword",keyword); 
        mav.setViewName("board/list"); //포워딩할 뷰의 이름
        mav.addObject("map", map); //ModelAndView에 map을 저장
        return mav; // board/list.jsp로 이동
    }




BoardDAOImpl.java [ countArticle (게시글 개수 구하는 메서드) ]

1
2
3
4
5
6
7
8
9
//레코드 갯수 계산 
    @Override
    public int countArticle(
            String search_option, String keyword) throws Exception {
        Map<String,String> map=new HashMap<>();
        map.put("search_option"search_option);
        map.put("keyword""%"+keyword+"%");
        return sqlSession.selectOne("board.countArticle",map);
    }
cs

sql에 여러가지 변수를 전달해야할 경우엔 DAO에서 HashMap에 묶어보낸다.


1
2
3
4
5
6
7
8
9
10
11
12
//게시물 목록 리턴
    @Override
    public List<BoardDTO> listAll(String search_option, String keyword,int start, int end) 
            throws Exception {
        Map<String,Object> map=new HashMap<>();
        map.put("search_option", search_option);
        map.put("keyword""%"+keyword+"%");
        map.put("start", start); //맵에 자료 저장
        map.put("end", end);
// mapper에는 2개 이상의 값을 전달할 수 없음(dto 또는 map 사용)        
        return sqlSession.selectList("board.listAll",map); 
    }
cs



boardMapper.xml


1
2
3
4
5
6
<!-- 레코드 갯수 계산 -->    
    <select id="countArticle" resultType="int">
        select count(*)
        from board b,member m    
        <include refid="search" />
    </select>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    <select id="listAll"
resultType="com.example.spring02.model.board.dto.BoardDTO">
        <include refid="paging_header" />
            select bno,title,writer,name,regdate,viewcnt,show
                  ,(select count(*) from reply where bno=b.bno) cnt                    
            from board b, member m
            <include refid="search" />
            order by bno desc   
        <include refid="paging_footer" />
    </select>
    
    <sql id="paging_header">
        select *
        from (
            select rownum as rn, A.*
            from (    
    </sql>
    
    <sql id="paging_footer">
            ) A
        ) where rn between #{start} and #{end}    
    </sql>
    


<sql> 태그는 sql을 재활용할 수 있게 해준다.


페이지 나누기할때 header와 footer는 항상 똑같기때문에 코드를 재활용할 수 있게 쓴다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <sql id="search">
        <choose>
            <when test="search_option != 'all' ">
                where b.writer=m.userid 
                    and show='Y'
                    and ${search_option} like #{keyword}            
            </when>
            <otherwise>
                where b.writer=m.userid 
                    and show='Y'
                    and (name like #{keyword} 
                        or title like #{keyword}
                        or content like #{keyword} )            
            </otherwise>
        </choose>
    </sql>





board/list.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
<!-- 페이지 네비게이션 출력 -->
    <tr>
        <td colspan="5" align="center">
            <c:if test="${map.pager.curBlock > 1}">
                <a href="#" onclick="list('1')">[처음]</a>
            </c:if>
            <c:if test="${map.pager.curBlock > 1}">
                <a href="#" onclick="list('${map.pager.prevPage}')">
                [이전]</a>
            </c:if>
            <c:forEach var="num" 
                begin="${map.pager.blockBegin}"
                end="${map.pager.blockEnd}">
                <c:choose>
                    <c:when test="${num == map.pager.curPage}">
                    <!-- 현재 페이지인 경우 하이퍼링크 제거 -->
                        <span style="color:red;">${num}</span>
                    </c:when>
                    <c:otherwise>
                        <a href="#" onclick="list('${num}')">${num}</a>
                    </c:otherwise>
                </c:choose>
            </c:forEach>
            <c:if test="${map.pager.curBlock < map.pager.totBlock}">
                <a href="#" 
                onclick="list('${map.pager.nextPage}')">[다음]</a>
            </c:if>
            <c:if test="${map.pager.curPage < map.pager.totPage}">
                <a href="#" 
                onclick="list('${map.pager.totPage}')">[끝]</a>
            </c:if>
        </td>
    </tr>
 



1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
 
$(function(){ //검색기능
    $("#btnWrite").click(function(){
        location.href="${path}/board/write.do";
    });
});
 
function list(page){
    location.href="${path}/board/list.do?curPage="+page;
}
 
</script>



list(page) 함수는 원하는 num 페이지로 이동시켜준다.




검색기능


list.jsp 검색폼 부분

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 검색폼 -->
<form name="form1" method="post"
    action="${path}/board/list.do">
    <select name="search_option">
        <option value="name"
<c:if test="${map.search_option == 'name'}">selected</c:if>
        >이름</option>
        <option value="title" 
<c:if test="${map.search_option == 'title'}">selected</c:if>
        >제목</option>
        <option value="content" 
<c:if test="${map.search_option == 'content'}">selected</c:if>
        >내용</option>
        <option value="all" 
<c:if test="${map.search_option == 'all'}">selected</c:if>
        >이름+내용+제목</option>
    </select>
    <input name="keyword" value="${map.keyword}">
    <input type="submit" value="조회">
</form>




옵션태그 안에서 selected 설정 => 검색 시 [ 제목 ]을 선택하고 검색하면 이 선택이 검색 후에도 고정됨.


브라우저에서 소스코드 확인 :  <option value="title" selected>제목</option>


Controller에 넘어갈때 search_option의 defaultValue는 "all"이고 keyword의 defaultValue는 ""