본문 바로가기

프로그래밍/JAVA & SPRING

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

반응형

글목록 수정

현재 게시물 목록을 보면 '이름'이 찍혀야 하는데 '아이디'가 보이고 있다.

이름(name)은 게시판(board) 테이블에는 없고

회원(member) 테이블에 있다.

 

-- 게시판 테이블과 회원 테이블 join

 

select bno, title, writer, name, regdate, viewcnt

from board b, member m

where b.writer=m.userid

order by bno desc;



이 코드를 mybatis에 기존 코드를 수정해 보자 (listAll)

board/list.jsp 에서 ${row.writer} => ${row.name}

 

페이지 나누기

delete from board

 

-- 게시물 1000개 입력 (pl /sql 코드를 돌려 입력한다.)

아이디는 member 테이블에 있는 아이디로 한다.

'||'는 문자와 숫자를 더해준다.

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,'gksmf');
    i := i+1;
  end loop;
end;
/

PL/SQL 프로시저가 성공적으로 완료되었습니다.

select count(*) from board; // 1000개 확인

 

오라클은 페이지 나누기를 하기 위해서 아래와 같이 조금 복잡한 방식으로 한다.

select 쿼리가 3번 들어간다.

-- rownum : 출력순서

select *
from (
    select rownum as rn, A.*
    from (
        select rownum, bno, title, regdate, viewcnt, name            
        from board b, member m
        where b.writer=m.userid
        order by bno desc

    ) A
) where rn between 1 and 10;

 

안쪽의 빨간색 구문이 중요하다 mybatis에서 바뀌는 부분이고

나머지는 바뀌지 않는다.

 

sql 구문 실행 순서는

from ~절이 처음

where ~ 절이 두번째

select ~

order by ~ 가 마지막으로 실행된다.

 

먼저 안쪽 selec~desc을 실행해보고

두번째 select rownum as rn, A.*   ~   ) A 를 실행하면 아래와 같이 보인다.

 

 

 

마지막 select ~ 절은 두번째 select~의 결과(RN)에서 10개씩(페이지당 게시물 수) 끊어서 가져온다.

select *   ~~  ) where rn between 1 and 10;

select *   ~~  ) where rn between 12 and 20;

 

 

나. 페이지 나누기

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

- 전체 게시물 수 : 991개

- 몇 페이지? : 100

  991 / 10 => 99.1 dhffla => 100

 

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

where rn between 1 and 10

1페이지 => 1 ~10

2페이지 => 11 ~20

... ...

11페이지 => 101 ~110

57페이지 => 561 ~570

99페이지 => 981 ~990

100페이지 => 991 ~1000

 

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

  1페이지 => (1-1) * 10  + 1 => 1

  2페이지 => (2-1) * 10  + 1 => 11

  7페이지 => (7-1) * 10  + 1 => 61

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

  1페이지 => 1 + 10  - 1 = 10

  2페이지 => 11 + 10  - 1 = 20

 

* 전체 페이지 블록수

전체페이지갯수 / 10

92 / 10  => 9.2 => 10개

 

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

[이전]11 12 13 14 15 16 17 18 19 20 [다음]

...

[이전]41 42 43 44 45 46 47 48 49 50 [다음]

[이전]51 52 53 54 55 56 57 58 59 60 [다음]

...

[이전]91 92  ==> 끝에는 2개 블록이 남는다.

 

 

현재 페이지가 속한 블록

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

 

1페이지 => 몇번째 블록?

  (1-1) / 10 + 1 => 1블록

9페이지 => 몇번째 블록?

  (9-1) / 10 + 1 => 1블록

 

11페이지 => 몇번째 블록?

  (11-1) / 10 + 1 => 2블록

20페이지 => 몇번째 블록?

  (20-1) / 10 + 1 => 2블록

 

89페이지 => 몇번째 블록?

  (89-1) / 10 + 1 => 9블록

 

91페이지 => 몇번째 블록?

  (91-1) / 10 + 1 => 10번째 블록

 

* 페이지 블록의 시작 번호

(현재블록-1) * 블록단위 + 1

1블록 => (1-1) * 10 + 1 => 1

2블록 => (2-1) * 10 + 1 => 11

9블록 => (9-1) * 10 + 1 => 81

10블록 => (10-1) * 10 + 1 => 91

 

* 페이지 블록의 끝 번호

(현재블록-1) * 블록단위 + 1

1블록 => (1) + 10 - 1 => 10

2블록 => (11) + 10 - 1 => 20

9블록 => (81) + 10 - 1 => 90

10블록 => (91) + 10 - 1 => 100 ???

 

 

 

16) Pager.java

 

// 페이지 나누기 관련 작업 클래스
public class Pager {
 public static final int PAGE_SCALE=10;  //페이지당 게시물수
 public static final int BLOACK_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; //다음 페이지 블록
 // where rn between #{start} and #{end}
 private int pageBegin; //#{start}
 private int pageEnd; //#{end}
 //[이전] blockBegin 42 43 44 45 46 47 48 49 blockEnd [다음]
 private int blockBegin; //현재 페이지 블록의 시작번호
 private int blockEnd; //현재 페이지 블록의 끝번호
 // 생성자
 // Pager(레코드 갯수, 현재 페이지 번호)
 public Pager(int count, int curPage) {
  curBlock=1;  // 현재 페이지 블록 번호
  this.curPage = curPage;// 현재 페이지 설정
  setTotPage(count); // 전체 페이지 갯수 계산
  // between #{start} and #{end}에 입력될 값 계산
  setPageRange();
  setTotBlock(); // 전체 페이지 블록 갯수 계산
  setBlockRange(); // 페이지 블록의 시작, 끝 번호 계산
 }
 public void setBlockRange() {
  // 페이지 100개, PAGE_SCALE=10; BLOACK_SCALE=10
  // 현재 페이지 34이면 curBlock = (34-1) / 10 + 1
  // curBlock - 현재 페이지가 몇번째 페이지 블록에 속하는지 계산
  curBlock = (int)Math.ceil((curPage-1)/BLOACK_SCALE -1); // 4번째 블럭
  // blockBegin, blockEnd-현재 페이지 블록의 시작, 끝 번호 계산
  blockBegin = (curBlock-1) * BLOACK_SCALE + 1;
  blockEnd = blockBegin + BLOACK_SCALE + 1;
  // 마지막 페이지 번호가 범위를 초과하지 않도록 처리
  if(blockEnd > totPage) blockEnd = totPage;
  // [이전]을 눌렀을 때 이동할 페이지 번호
  prevPage = (curBlock == 1) ? 1 : (curBlock - 1) * BLOACK_SCALE;
  // [다음]을 눌렀을 때 이동할 페이지 번호
  nextBlock = (curBlock > totBlock) ? 
    (curBlock * BLOACK_SCALE) : curBlock * BLOACK_SCALE + 1; 
  // 마지막 페이지가 범위를 초과하지 않도록 처리
  if(nextPage >= totPage) nextPage = totPage;
 }
  
 public void setPageRange() {
  // where rn between #{start} and #{end}에 입력될 값
  //시작번호=(현재페이지 - 1) * 페이지당 게시물수 + 1
  //끝번호 = 시작번호 + 페이지당 게시물수 - 1
  pageBegin = (curPage-1) * PAGE_SCALE + 1;
  pageEnd = pageBegin + PAGE_SCALE -1;
 }
// getter / setter()
...
 public void setTotPage(int count) {
  // Math.ceil(실수)올림 처리
  this.totPage = (int)Math.ceil(count * 1.0 / PAGE_SCALE);
 }
...
 //페이지 블록의 갯수 계산(총 100 페이지라면 10개 블록)
 public void setTotBlock() {
  this.totBlock = (int)Math.ceil(totPage / PAGE_SCALE);
 }
...
}

 

 

컨트롤러 수정.

 

@RequestMapping("list.do")
 public ModelAndView list() throws Exception {..}
 
 
@RequestMapping("list.do")
 public ModelAndView list(
   @RequestParam(defaultValue="1") int curPage,
   @RequestParam(defaultValue="all") String search_option,
   @RequestParam(defaultValue="") String keyword ) 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(0, 0, "", ""); // 목록
 
List<BoardDTO> list = boardService.listAll(start, end, search_option, keyword);
// 컨트롤러 수정 후 dao에 코드를 추가로 작성해야 한다.

 

 

BoardDAOImpl 수정.

 

 public List<BoardDTO> listAll(int start, int end, 
   String search_option, String keyword) 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);
  return sqlSession.selectList("board.listAll", map);
 }

 

 

추카 코드

 

  map.put("count", count);
  map.put("search_option", search_option);
  map.put("keyword", keyword);
  map.put("pager", pager);

 

 

boardMapper.xml

<sql>태그는 sql의 부분이다. 재활용을 할 수 있도록 되어있다.

${search_option}에서 '$'을 붙이면 따옴표가 빠진다

#{keyword}에서 '#'는 따옴표를 붙인다.

 

 <sql id="search">
  <choose>
   <when test="search_option == 'all'">
    where 
     b.writer = m.userid
     and
     ( name like '%'||#{keyword}||'%'
      or content like '%'||#{keyword}||'%'
      or title like '%'||#{keyword}||'%')
   </when>
   <otherwise>
    where b.writer=m.userid and
    ${search_option} like '%'||#{keyword}||'%'
   </otherwise>
  </choose>
 </sql>
 <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>

 

 

boardMapper.xml의 listAll 수정

 

<select id="listAll" resultType="com.example.spring02.model.board.dto.BoardDTO">
  select bno, title, writer, name, regdate, viewcnt
  from board b, member m
  where b.writer=m.userid ( include 'search 절로 대체)
  order by bno desc
 </select>

아래와 같이 수정한다.

 <select id="listAll" resultType="com.example.spring02.model.board.dto.BoardDTO">
  <include refid="paging_header"/>
  select bno, title, writer, name, regdate, viewcnt
  from board b, member m
  where b.writer=m.userid
  <include refid="search" />
  order by bno desc
  <include refid="paging_footer" />
 </select>

 

 

 

board/list.jsp 수정

<!-- 페이지 네비게이션 -->

1부터 끝(100)까지 나오고 클릭했을때 페이지 전환이 되는지 확인한다.

 

<c:forEach var="num" begin="1" end="${map.pager.totPage}">
  <a href="javascript:list('${num}')">${num}</a>
</c:forEach>

 

잘 작동한다면 아래처럼 코드를 수정해 본다.

[처음][이전]페이지 블럭(blockBigin~blockEnd)[다음][끝]

 

    <c:if test="${map.pager.curBlock > 1}">
     <a href="javascript:list('1')">[처음]</a>
    </c:if>
    <c:if test="${map.pager.curBlock > 1}">
     <a href="javascript: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="javascript:list('${num}')">${num}</a> 
      </c:otherwise>
     </c:choose>
    </c:forEach>
    <c:if test="${map.pager.curBlock < map.pager.totBlock }">
     <a href="javascript:list('${map.pager.nextPage}')">[다음]</a>
    </c:if>
    <c:if test="${map.pager.curPage < map.pager.totPage }">
     <a href="javascript:list('${map.pager.totPage}')">[끝]</a>
    </c:if>

 

 

전체게시물 갯수(count)를 구하는 로직을 구현한다.

 

// 레코드 갯수 계산
  int count = boardService.countArticle(search_option, keyword);

 

 

BoardDAOImpl.java 수정

 public int countArticle(String search_option, String keyword) throws Exception {
  Map<String, Object> map = new HashMap<>();
  map.put("search_option", search_option);
  map.put("keyword", keyword);
  return sqlSession.selectOne("board.countArticel", map);
 }

 

 

boardMapper.xml 수정

 <select id="countArticel" resultType="int">
  select count(*) 
  from board b, member m 
  <include refid="search" />
 </select>

 

 

 

레코드 81개를 삭제하고 게시물 수를 확인해 보자

delete from board where bno between 10 and 90;

 

검색기능을 구현해본다.