ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [spring] 페이징 처리하기 (feat. Thymeleaf)
    Spring 2020. 2. 4. 18:48
    반응형

    페이징 처리는 웹 개발에서 필수적인 기술이며, 요즘은 무한스크롤로 처리하는 경우도 많다.
    무한스크롤 + 페이징을 접목하는 경우도 다수 본 것 같다. (...나중에 써봐야징..)

    페이징을 처리하는 방법은 여러가지가 있지만, 간단한 예제를 통해서 가장 기본이 되는 방식으로 구현해 보자 !!

     

    방긋 방긋

    개발 환경은 아래와 같다..!

    언어 : java 1.8
    프레임워크 : springboot 2.2.4.RELEASE (spring-boot-starter)
    템플릿엔진 : thymeleaf 2.2.4.RELEASE (spring-boot-starter)
    데이터베이스 : H2 1.4.200 (spring-boot-starter)
    orm : data-jpa 2.2.4.RELEASE (spring-boot-starter)

     


    사전준비

    페이징처리를 test 하기 위해 대량의 더미데이터를 넣어주고, bootstrap을 이용해서
    간단한 table과 페이징 버튼 생성하기
    페이징 버튼
     
    테이블 // 코로나 바이러스 물러가라~ (불러가라 오타... 주륵 TT..)

     


    앤티티 설계

     

    Board.class

    package com.devyu.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    
    @Entity
    public class Board {
    
    	@Id @GeneratedValue
    	private String id; // id
    	
    	private String title; // 제목
    	
    	private String user; // 작성자
    
    	
        // Getter
        
        // Setter
    }
    

     

    실제 이런식으로 설계하면 큰일 나겠지만 페이징처리가 목적이니 간단하게 설계하였다. (심지어 content도 안만듬 ㅋ)

    (id, title, user .. 진짜 간단 머쓱..)

    Pagination.java

    public class Pagination {
    
    	/** 1. 페이지 당 보여지는 게시글의 최대 개수 **/
    	private int pageSize = 10;
    
    	/** 2. 페이징된 버튼의 블럭당 최대 개수 **/
    	private int blockSize = 10;
    
    	/** 3. 현재 페이지 **/
    	private int page = 1;
    
    	/** 4. 현재 블럭 **/
    	private int block = 1;
    
    	/** 5. 총 게시글 수 **/
    	private int totalListCnt;
    
    	/** 6. 총 페이지 수 **/
    	private int totalPageCnt;
    
    	/** 7. 총 블럭 수 **/
    	private int totalBlockCnt;
    
    	/** 8. 블럭 시작 페이지 **/
    	private int startPage = 1;
    
    	/** 9. 블럭 마지막 페이지 **/
    	private int endPage = 1;
    
    	/** 10. DB 접근 시작 index **/
    	private int startIndex = 0;
    
    	/** 11. 이전 블럭의 마지막 페이지 **/
    	private int prevBlock;
    
    	/** 12. 다음 블럭의 시작 페이지 **/
    	private int nextBlock;
        
        // Getter
        // Seteer
    }

    우선 페이징을 처리하는 Pagination 클래스를 생성한다. 변수 생성하고 주석도 달아 놓았지만, 내가 쓰고도 내가 이해하기 어려운 주석들이 있어서 추가하자면.. (setter, getter는 스크롤을 너무 잡아먹기에 생략..)

    주석 번호 설명
    2번 - int blockSize
    // 페이징된 버튼의 블럭당 최대 개수 ?
    위 캡쳐와 같이 페이징된 버튼들이 노출되는 최대 개수를 정해주는것. 블럭사이즈의 개수를 정해주지 않는다면 페이지가 UI상에서 끝도없이 늘어만 간다. 적절하기 개수 제한을 해주는 것이 바람직한듯?


    ex)
    위의 예시는 5개로 제한을 해준 것이며, 총 페이징 수가 14개일 경우,
    [1 2 3 4 5] --- [5 6 7 8 9 10] --- [11 12 13 14]
    7번 - int totalBlockCnt // 총 블럭 수 ?
    blockSize로 인해서 생성된 블럭의 총 개수를 의미한다.


    ex)
    총 페이징 수가 14개이고 blockSize가 5일 경우
    [1 2 3 4 5] --- [6 7 8 9 10] --- [11 12 13 14]
    총 블럭 수 는 3개 된다.
    10번 - int startIndex // DB 접근 시작 index ?
    DB에 접근해서 게시물을 select할 때, 어디서부터 가져올 것인지에 대한 offset 역할을 해주는 부분

    이어서 Pagination class에 페이칭 처리를 하는 메소드를 추가하자.

     

    Pagination.java

    public class Pagination {
    
    ...
    	// 위에서 정의한 변수
    ...
    
        // setter
        // getter
    
    		public Pagination(int totalListCnt, int page) {
    
    		// 총 게시물 수와 현재 페이지를 Controller로 부터 받아온다.
    
    		// 총 게시물 수	- totalListCnt
    		// 현재 페이지	- page
    		
    		
    		/** 3. 현재 페이지 **/
    		setPage(page);
    		
            
    		/** 5. 총 게시글 수 **/
    		setTotalListCnt(totalListCnt);
    
    
    		/** 6. 총 페이지 수 **/
    		// 한 페이지의 최대 개수를 총 게시물 수 * 1.0로 나누어주고 올림 해준다.
    		// 총 페이지 수 를 구할 수 있다.
    		setTotalPageCnt((int) Math.ceil(totalListCnt * 1.0 / pageSize));
    
    
    		/** 7. 총 블럭 수 **/
    		// 한 블럭의 최대 개수를 총  페이지의 수 * 1.0로 나누어주고 올림 해준다.
    		// 총 블럭 수를 구할 수 있다.
    		setTotalBlockCnt((int) Math.ceil(totalPageCnt * 1.0 / blockSize));
    		
            
    		/** 4. 현재 블럭 **/
    		// 현재 페이지 * 1.0을 블록의 최대 개수로 나누어주고 올림한다.
    		// 현재 블록을 구할 수 있다.
    		setBlock((int) Math.ceil((page * 1.0)/blockSize)); 
    
    
    		/** 8. 블럭 시작 페이지 **/
    		setStartPage((block - 1) * blockSize + 1);
    		
            
    		/** 9. 블럭 마지막 페이지 **/
    		setEndPage(startPage + blockSize - 1);
    		
            
    		/* === 블럭 마지막 페이지에 대한 validation ===*/
    		if(endPage > totalPageCnt){this.endPage = totalPageCnt;}
    		
            
    		/** 11. 이전 블럭(클릭 시, 이전 블럭 마지막 페이지) **/
    		setPrevBlock((block * blockSize) - blockSize);
    
    
    		/* === 이전 블럭에 대한 validation === */
    		if(prevBlock < 1) {this.prevBlock = 1;}
    
    
    		/** 12. 다음 블럭(클릭 시, 다음 블럭 첫번째 페이지) **/
    		setNextBlock((block * blockSize) + 1);
    		
            
    		/* === 다음 블럭에 대한 validation ===*/
    		if(nextBlock > totalPageCnt) {nextBlock = totalPageCnt;}
    		
            
    		/** 10. DB 접근 시작 index **/
    		setStartIndex((page-1) * pageSize);
    	
    	}
    }

     

    Controller 에서 전체 게시물 수(totalListCnt)와 현재 페이지(page)를 생성자에서 받고 setting을 시작한다.

    주석에 달린 숫자의 의미는 위에서 정의한 변수에 해당하는 번호로 편의를 위해 달아두었다.

    코드 중간에 validation이 걸려있는 코드가 있는데 처음에 깜빡하고 그냥 작업하다가 [이전] 페이지를 눌렀는데 0 페이지를 호출하는 error가 발생했다. 잊지말고 validation을 해줘야 예상하지 못한 error를 막을 수 있다.:)

    validation 편안..

     

    이 정도로 Pagination classs 설정을 마치고 Controller와 Repository 를 함께 살펴보자.

     

    HomeController.java

    @GetMapping("/")
    public String home(Model model, @RequestParam(defaultValue = "1") int page) {
    
        // 총 게시물 수 
        int totalListCnt = boardRepository.findAllCnt();
    
        // 생성인자로  총 게시물 수, 현재 페이지를 전달
        Pagination pagination = new Pagination(totalListCnt, page);
    
        // DB select start index
        int startIndex = pagination.getStartIndex();
        // 페이지 당 보여지는 게시글의 최대 개수
        int pageSize = pagination.getPageSize();
    
        List<Board> boardList = boardRepository.findListPaging(startIndex, pageSize);
    
        model.addAttribute("boardList", boardList);
        model.addAttribute("pagination", pagination);
    
        return "index";
    }

     

    BoardRepository.java

    @Repository
    public class BoardRepository {
    
    	@PersistenceContext
    	private EntityManager em;
    
    	public int findAllCnt() {
    		return ((Number) em.createQuery("select count(*) from Board")
    					.getSingleResult()).intValue();
    	}
    
    	public List<Board> findListPaging(int startIndex, int pageSize) {
    		return em.createQuery("select b from Board b", Board.class)
    					.setFirstResult(startIndex)
    					.setMaxResults(pageSize)
    					.getResultList();
    	}
    }
    

     

    진행과정

    1. @RequestParam(dafault = "1") int page 를 통해 해당 요청 파라미터를 받는다. (default는 1)
    2. Repository에서 총 게시물의 수를 select 해온다.
    3. 1번 과정의 현재 페이지와 2번 과정의 총 게시물 수를 Pagination를 매개변수로 생성한다.
    4. 위에 짜둔 Pagination 메서드로 인해서 쨔르르르~ 페이징에 필요한 변수 세팅이 완료 된다.
    5. 세팅이 완료된 Pagination의 변수중에서 startIndex와 pageSize를 통해 DB에서 얼만큼의 게시물들을 가져올지를 설정한다.
    6. 페이징 처리가 완료된 List를 view단에 뿌려준다.
    (참고)
    toString으로 Pagination class를 찍어보면.. (default는 1page)

    Pagination [
    pageSize=10, blockSize=10, page=1, block=1,
    totalListCnt=189, totalPageCnt=19, totalBlockCnt=2, 
    startPage=1, endPage=10, startIndex=0, prevBlock=1, nextBlock=11
    ]

     

    이제 마지막으로 view단을 통해 페이징 처리가 된 데이터를 뿌려줄 것이다. 야호 절반은 끝났다 !!

    템플릿엔진 thymeleaf를 사용해서 구현해보ㅉ  ㅏ

     

    index.html

    <!DOCTYPE html>
    <html 	lang="ko" 
            xmlns="http://www.w3.org/1999/xhtml" 
            xmlns:th="http://www.thymeleaf.org">
    			
    <head>
    	<meta charset="UTF-8">
    	<title>paging</title>
    		<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    	</head>
    <body>
     
    <table class="table table-striped">
    
      <thead class="thead-dark">
        <tr>
          <th scope="col" style="width: 10%">no</th>
          <th scope="col">제목</th>
          <th scope="col" style="width: 15%">작성자</th>
        </tr>
      </thead>
      
      <tbody>
        <tr th:each="board : ${boardList}">
          <th scope="row" th:text="${boardStat.index + 1}">1</th>
          <td th:text="${board.title}"></td>
          <td th:text="${board.user}"></td>
        </tr>
      </tbody>
      
    </table>
    
    // paging button
    <nav aria-label="Page navigation example ">
      <ul class="pagination">
      <li class="page-item">
          <a class="page-link" th:href="@{/?page=1}" aria-label="Previous">
            <span aria-hidden="true">처음</span>
          </a>
        </li>
        <li class="page-item">
          <a class="page-link" th:href="@{/?page={page} (page = ${pagination.prevBlock})}" aria-label="Previous">
            <span aria-hidden="true">이전</span>
          </a>
        </li>
        <th:block  th:with="start = ${pagination.startPage}, end = ${pagination.endPage}">
    	    <li class="page-item" 
    	    		 th:with="start = ${pagination.startPage}, end = ${pagination.endPage}"
    	    		th:each="pageButton : ${#numbers.sequence(start, end)}">
    	    		<a class="page-link" th:href="@{/?page={page} (page = ${pageButton})}" th:text=${pageButton}></a>
    	    </li>
        </th:block>
        <li class="page-item">
          <a class="page-link" th:href="@{?page={page} (page = ${pagination.nextBlock})}" aria-label="Next">
            <span aria-hidden="true">다음</span>
          </a>
        </li>
        <li class="page-item">
          <a class="page-link" th:href="@{?page={page} (page = ${pagination.totalPageCnt})}" aria-label="Previous">
            <span aria-hidden="true">끝</span>
          </a>
        </li>
      </ul>
    </nav>
    
    </body>
    </html>

    thymeleaf를 사용하기 위해서 html 태그에

    <html 	lang="ko" 
            xmlns="http://www.w3.org/1999/xhtml" 
            xmlns:th="http://www.thymeleaf.org">

    꼭 잊지 말고 넣어주장. 안넣으면 thymeleaf 못씀 ㅋ..

     

    controller단에서 만들고 model을 통해 view단으로 보내준 데이터를 thymeleaf 문법에 맞도록 넣어주기만 하면 완성할 수 있다.

     - 버튼 상세 설명 - 
    [처음] 버튼을 누르면 1페이지로 이동
    [다음] 버튼을 누르면 다음 block의 첫 페이지로 이동
    [끝] 버튼을 누르면 마지막 페이지로 이동
    [이전] 버튼을 누르면 이전 block의 마지막 페이지로 이동

    추가적으로 thymeleaf에 대해서 자세히 알고 싶다면 !

    https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#literals

     

    Tutorial: Using Thymeleaf

    1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text. The main goal of Thymeleaf is to provide a

    www.thymeleaf.org

     

    마지막으로 view단을 확인하는 것으로 페이징 처리 끝~~~

    paging 처리 완료
    별탈 없이 완성..!

    반응형

    댓글

Designed by Tistory.