본문 바로가기

스프링

스프링(SPRING) 게시판 3탄(게시판 페이징처리)

반응형

이번엔 저번 글들에서 다루지 않았던 게시판의 꽃 페이징을 다루어보도록 하겠다.

자고로 게시판이라고 하면 페이징 기능이 제일 먼저 떠오르는데 지금까지 게시판인데 페이징이 없는 게시판은 보지 못한 거 같다. 그만큼 게시판에서 가장 중요한 기능인 페이징 처리를 해보자.

지금 보여주는 자료는 저번 스프링 게시판 1탄, 2탄 글에서 설명한 프로젝트와 같다. 예시로 회원가입만 보여줬지만, 페이징을 설명 안 하자니 게시판 설명이라고 할 수 없는 거 같아 추가하게 되었다.

먼저 페이징 처리는 https://www.w3schools.com에서 w3.css나 부트스트랩에 들어가 보면 Pagination이라고

예시를 보면 간단하게 사용 가능하다. 원하는 pagination을 가지고와 사용해보도록 하자.

- Pagination 사용

페이징 적용 예시

위 사진을 보면 게시판을 들어왔을 때 보이는 첫 화면인데 게시글이 10개가 보이고 페이징은 1페이지를 가리키고 있다.

이처럼 모든 게시판들은 처음 들어가면 1페이지의 고정된 게시글수가 보이도록 하는데 이는 보여줄 게시글 수를 고정해서 페이징 처리를 하기 때문이다.

필자는 게시판은 만들어보지 않았을 때 글 전체를 다 가져오는 줄 알았다.

하지만 그렇게한다면 사이트는 엄청나게 느려질 것이고, 글이 많으면 많아질수록 굉장히 과부하가 걸릴 것이다.

그렇기 때문에 게시판은 글 개수를 정하여 예시로 10개의 글 개수를 보여준다고 하면 총게시글을 10개씩 보여준다면 몇 페이지가 만들어질까를 적용해서 페이징을 할 때마다 그 구간에 맞는 글 정보를 가져오는 것이다.

말로 설명하면 어려우니 직접 눈으로 예시를 본다면 이해가 빠를 것이다. 

<div class="w3-bar">
  <a href="#" class="w3-button">&laquo;</a>
  <a href="#" class="w3-button">1</a>
  <a href="#" class="w3-button">2</a>
  <a href="#" class="w3-button">3</a>
  <a href="#" class="w3-button">4</a>
  <a href="#" class="w3-button">&raquo;&raquo;</a>
</div>

위는 w3.css에서 가져온 예시이다. 페이징 자체는 별게없다. 그냥 양식에 맞춰 원하는 값을 a태그 안에 적으면 된다.

가장 중요한 것은 페이징 정보를 활용해서 정확한 페이징을 하는 것이다. 

먼저 페이징에서 사용할 클래스에 대해 설명하겠다.

페이징에 필요한 것은 두가지가 있는데 먼저 SQL에서 사용할 Criteria 클래스와 페이징에서 사용할 PageingInfo 클래스가 있다.

- 페이징 처리 클래스

Criteria 클래스

@Getter
@Setter
@ToString
public class Criteria { 	// 페이징에 필요한 클래스
	private int pageNum; 	// 페이지
	private int amount; 	// 한번에 보여줄 게시물 수

	private String type; 	// 검색종류
	private String keyword; // 검색어

	public Criteria() {
		this(1, 10);
	}

	public Criteria(int pageNum, int amount) {
		this.pageNum = pageNum;
		this.amount = amount;
	}

	public String[] getTypeArr() {
		return type == null ? new String[] {} : type.split("");
	}

	public String getListLink() {
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("").queryParam("pageNum", this.pageNum)
				.queryParam("amount", this.amount).queryParam("type", this.type).queryParam("keyword", this.keyword);

		return builder.toUriString();
	}
}

먼저 SQL에서 사용 할 Criteria 클래스에 대해 알아보자

Criteria에는 변수가 4가지가 있는데, 페이징은 변수 pageNum으로도 충분히 가능하지만 이번 게시판에서는 게시글 검색 기능과 보여주는 게시글 수 를 추가해주었다.

먼저 게시판페이지를 호출했을 때 Criteria를 만들어준다.

@GetMapping("/board") // 게시판 호출(게시판글 리스트와 페이징정보)
	public void getBoardPage(Criteria cri, Model model) {
    
	}

파라미터로 Criteria를 받아주는데 이때 게시판 첫 호출일 경우 페이징 정보는 존재하지 않을 것이다.

그렇기 때문에 Criteria 생성자를 이용해 pageNum은 1로 amount는 보여주고 싶은 수(10)로 설정해준다.

그다음 SQL문으로 정보를 가져와 보여주면 우리가 보는 1페이지에 10개의 게시물이 게시판에 보이게 되는 것이다.

이제 SQL문을 어떻게 짜야할지 궁금해질 텐데 SQL도 바로 보도록 하자.

- 페이징 SQL

BoardMapper.xml 의 일부

<sql id="criteria">
		<trim prefix="and(" suffix=") " prefixOverrides="OR">
			<foreach item='type' collection="typeArr">
				<trim prefix="OR">
					<choose>
						<when test="type== 'T'.toString()">
							post_title like '%' || #{keyword}||'%'
						</when>
						<when test="type== 'C'.toString()">
							post_content like '%' || #{keyword}||'%'
						</when>
						<when test="type== 'W'.toString()">
							post_writer like '%' || #{keyword}||'%'
						</when>
					</choose>
				</trim>
			</foreach>
		</trim>
	</sql>

	<select id="selectboardList" resultType="board">
		select 
			* 
		from 
			(select
				 rownum rno,b.* 
			from 
				board_post b 
			where 
				isshow != 'N'
		<include refid="criteria"></include>
			order by 
				post_no desc
			)
		 where
		 	rno between (#{pageNum}-1)*#{amount}+1 and (#{pageNum})*#{amount}
	</select>

자 <select id="selectboardList"> SQL문을 보면 우리가 호출할 때 생성자를 통해 1과 10으로 초기화시켜둔 pageNum과 amout가 보인다.

테이블에서 원하는 정보, ROWNUM을 꺼내 글 번호(내림차순)로 정렬한 후 ROWNUM을 통해서

ROWNUM이 pageNum과 amout로 계산하여  1부터 10까지 인 게시글 정보를 가져오는 것이다.

이때 정렬을 하려면 인라인 뷰로 정렬을 한 후에 ROWNUM을 붙여주고 해야 한다. 만약 ROWNUM을 붙이고 정렬을 해버리면 순서는 뒤죽박죽이 돼버린다. 기억해서 실수하지 않도록 하자~

그렇게 페이지를 가져온 SQL문을 보았다. 그런데 설명한 SQL문 위에 trim을 사용한 구문이 보일 것이다. 저것은 무엇이냐면, 검색어와 키워드를 사용해서 검색 기능 SQL문이다.

trim과 foreach를 사용해서 검색으로 페이징 호출을 했을 때, type이 ( T 제목, C 내용, W 작성자 , TC 제목+내용 )으로 호출 시 Criteria에 담아 가지고 와서 type에 맞는 조건을 검색어로 검색해서 페이징을 만들어준다.

 

이제 페이지에 맞는 게시물 정보를 가지고 왔지만 페이징처리를 하기는 아직 부족하다.

페이징 처리를 하기 위해서는 페이지가 몇 개가 존재하는지, 그리고 다음 버튼이나 이전 버튼이 페이지가 존재하지 않는 구간에서도 생성되면 눌렀을 때 오류가 날것이다.

그렇기 때문에 페이지정보를 담아주는 PageingInfo 클래스가 필요하다. 

밑에 눈으로 봐보자. 

PageingInfo 클래스

@Getter
@ToString
public class PageingInfo {
   // 보고있는 페이지에서 시작번호 ( 예) 10개씩이라고하면 1 , 11 , 21 , 31 ..... )
   private int startPage;
   // 보고있는 페이지에서 끝번호 ( 예) 10개씩이라고하면 10, 20, 30 ,40 ,50 .....)
   private int endPage;
   // 이번페이지가 있는지 다음페이지가 있는지 확인
   private boolean prev,next;
   
   private int lastPage;
   // 전체 게시글 수
   private int total;
   // 요청이 온 페이지의 페이지번호와 몇개씩 보고 싶은지
   private Criteria cri;
   
   public PageingInfo(Criteria cri, int total) {
      this.cri = cri; // 요청 정보
      this.total = total; // 전체 게시글 수
      
      this.endPage = (int) (Math.ceil(cri.getPageNum() / 10.0))*10;
      this.startPage = this.endPage - 9;
      
      int realEnd = (int) (Math.ceil((total*1.0)/cri.getAmount()));
      
      lastPage = realEnd;
      
      if(realEnd < this.endPage) {
         this.endPage = realEnd;
      }
      
      this.prev = this.startPage > 1;
      this.next = this.endPage < realEnd;
   }
}

위 PageingInfo 클래스를 보면 페이지 정보를 담는 변수가 존재한다. 이 클래스는 전에 사용했던 Criteria와 총 게시물 수를 가지고 만드는데 총 게시물 수는 Criteria로 게시물 정보를 가져오는 동시에 검색해주고 가져온다.

BoardMapper.xml 의 일부

<select id="countTotal" resultType="int">
	select 
		count(*) 
	from 
		board_post 
	where 
		isshow != 'N'
	<include refid="criteria"></include>
</select>

똑같이 검색정보도 적용해주어 검색된 게시물의 총 개수를 구한다.

이 정보를 사용해서 PageingInfo를 세팅하는데, 먼저 가장 끝 페이지를 구한다.

가져온 총 게시물 수를  Math.ceil(소수점 첫째자리 올려주는) 함수를 이용해 pageNum이 1일 경우 1/10을 하면 0.1이지만 Math.ceil로 인해 1이 되고 *10을 해서 endPage는 10이 된다.

그리고 startPage는  계산한 endPage-9를 해서 1이 된다. 

변수 realEnd는 게시물의 총 개수와 보여주는 amout를 나누어 말 그대로 가장 끝 페이지를 구하는데 여기서도 Math.ceil함수를 사용해서 게시물이 없는 페이지를 방지해준다.

그리고 세팅한 변수들을 비교해서 prev는 startPage가 1보다 클 경우에만 보이도록 해 주고, next는 현재 끝페이지(endPage)가 마지막 페이지(realEnd)가 아닐 때만 보이도록 해준다.

자 이제 여기까지 세팅했으면 호출했을때 세팅해서 값을 보내주자.

@GetMapping("/board") // 게시판 호출(게시판글 리스트와 페이징정보)
	public void getBoardPage(Criteria cri, Model model) {
		model.addAttribute("list", service.getPostList(cri));
		model.addAttribute("pageMaker", new PageingInfo(cri, service.countTotal(cri)));
}

 Model 에 담아서 jsp에서 JSTL로 가져온 데이터로 뿌려주기만 하면 된다.

- 페이지 처리

board.jsp 의 일부

<div class="boardContainer">
        <div class="boardBox">
            <div class="pull-right amountDiv">
                <select name="" id="amount">
                    <option value="">--</option>
                    <option value="10">10개씩</option>
                    <option value="20">20개씩</option>
                    <option value="30">30개씩</option>
                </select>
            </div>
            <table class="table table-striped boardTable">
                <thead>
                    <th class="th100">글번호</th>
                    <th class="th100">카테고리</th>
                    <th>제목</th>
                    <th>작성자</th>
                    <th class="th100">작성일</th>
                    <th class="th100">조회수</th>
                </thead>
                <tbody>
                    <c:forEach var="post" items="${list}">
						<tr>
							<td>${post.postNo}</td>
							<td>${post.postCategory}</td>
							<td><a class="postGo" href="/bo/post?postNo=${post.postNo}">${post.postTitle}</td>
							<td>${post.postWriter}</td>
							<td>${post.postDate}</td>
							<td>${post.postHit}</td>
						</tr>
					</c:forEach>
                </tbody>
            </table>
            <div class="searchDiv">
                <label class="searchLabel">검색</label> 
                <input type="text" id="keyword">
                <button id="searchBtn" name="searchBtn" type="submit" class="btn"></button>
                <select id="type">
                    <option value="TC" ${pageMaker.cri.type eq 'TC' ? 'selected' : ''}>제목+내용</option>
                    <option value="T" ${pageMaker.cri.type eq 'T' ? "selected" : ''}>제목</option>
                    <option value="C" ${pageMaker.cri.type eq 'C' ? "selected" : ''}>내용</option>
                    <option value="W" ${pageMaker.cri.type eq 'W' ? "selected" : ''}>작성자</option>
                    <option value="TWC" ${pageMaker.cri.type eq 'TWC' ? "selected" : ''}>전체</option>
                </select>
            </div>
            <div class="pull-right writeBtnDiv">
            	<c:if test="${loginInfo != null}">
	                <a href="/bo/write"><button>글쓰기</button></a>        	
            	</c:if>
            </div>
            <div class="paginationDiv">
                <ul class="pagination">
					<c:if test="${pageMaker.prev }">
						<li class="paginate_button previous">
							<a href="${pageMaker.startPage-1 }">이전</a>
						</li>
					</c:if>
					<c:forEach var="num" begin="${pageMaker.startPage }" end="${pageMaker.endPage }">
						<li class="paginate_button ${pageMaker.cri.pageNum == num? "active":"" }">
							<a href="${num }">${num}</a>
						</li>
					</c:forEach>
					<c:if test="${pageMaker.next }">
						<li class="paginate_button next">
							<a href="${pageMaker.endPage+1 }">다음</a>
						</li>
					</c:if>
					<c:if test="${pageMaker.next }">
						<li class="paginate_button next">
							<a href="${pageMaker.lastPage }">맨끝</a>
						</li>
					</c:if>
                </ul>
            </div>
        </div>
    </div>

가져온 페이지 정보로 pagination을 생성해준다. 

forEach를 돌려서 startPage부터 EndPage까지 번호를 생성해준다 ㅎㅎ

그 번호를 사용해서 해당 페이지를 눌렀을 때 다시 호출하면 페이징 처리가 완벽하게 이루어진다. ~~

게시판 검색
게시판 게시글 수

Criteria 클래스의 변수 amout를 이용해서 보여주는 게시글 수를 정할 수 있다.

게시판 마지막페이지

페이징 잘된다 ㅎㅎ 

여기서 응용해서 게시글의 댓글에도 페이징을 적용할 수 있다.

게시판 댓글 페이징

게시글 내에서 댓글에 페이징을 똑같이 적용해주면 가능하다. ㅎㅎ

댓글은 LEVEL을 사용해서 대댓글(계층형 쿼리)을 표현해주었다.

어디서든 응용 가능한 페이징 처리!! 굉장히 쓸모 있으니 연습해보며 알아두자 

그럼 이만 스프링 게시판을 마치겠다.

반응형