Spring Boot/STUDY

[Spring Boot] JPA 프로젝트 - 페이징 기능 구현

코맹 2024. 6. 20. 13:59

/repository/BoardRepository.java findAll(pageable) 인터페이스 메서드 작성
// 페이징을 위한 네임스페이스
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

// 인터페이스만 있어도 CRUD가  가능
@Repository
public interface BoardRepository extends JpaRepository<Board, Long>{
  // 페이징용 JPA 쿼리 자동생성 인터페이스 메서드 작성
  @SuppressWarnings("null") // 경고 메시지 없애주는 어노테이션
  Page<Board> findAll(Pageable pageable);
}

 

/service/BoardService.java getList(page) 메서드 작성
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

@Service
@RequiredArgsConstructor
public class BoardService {
	private final BoardRepository boardRepository;
    
    public Page<Board> getList(int page) {
        Pageable pageable = PageRequest.of(page, 10);   // pageSize를 동적으로도 변경할 수 있음. 나중에...
        return this.boardRepository.findAll(pageable);

      }
 }
  • Spring-Data-JPA 라이브러리의 Page와 Pageable을 이용
더보기

💡 Spring Data JPA 제공 메서드


  • Pageable
    • Pageable은 Spring JPA에서 DB 쿼리에 쉽고 유연하게 limit 쿼리를 사용할 수 있게 해준다.
    • 특히 JPA를 사용할 때, 자동으로 Pageable 타입의 변수를 넘겨주면 JPA가 DB에 접근해 데이터를 가져올 때 자동으로 limit 조건을 붙여 데이터를 가져온다.
  • Page
    • Pageable을 파라미터로 하여 가져온 결과물은 Page<SomeObject> 형태로 반환되며
    • Page를 사용한다면 대부분 다수의 row를 가져오기 때문에 의 Page<List<SomeObject>> 형태로 반환한다.

 

더보기

📑 Pageable과 PageRequest

  • Pageable과 PageRequest는 Spring Data에서 제공하는 페이지네이션 정보를 담기 위한 인터페이스 구현체이다.
  • 페이지 벊로와 단일 페이지의 개수를 담을 수 있다.
  • 이를Spring Data JPA 레포지토리의 파라미터로 전달하여, 반환되는 엔티티의 컬렉션에 대해 페이징할 수 있다.

 

<참고자료>

https://velog.io/@dani0817/Spring-Boot-%ED%8E%98%EC%9D%B4%EC%A7%95Paging-%EC%A0%81%EC%9A%A9

 

 

/controller/BoardController.java list() 메서드 수정
  • 수정 전
  @GetMapping("/list")
  public String list(Model model) {
    List<Board> boardList = this.boardService.getList();    // Thymeleaf, JSP, mustache 등 VIEW로 보내는 기능
    model.addAttribute("boardList", boardList);

    
    return "board/list";    // templates/board/list.html 랜더링해서 리턴해라!
  }

 

  • 수정 후
  @GetMapping("/list")
  public String list(Model model, @RequestParam(value="page", defaultValue = "0") int page) {
    Page<Board> paging = this.boardService.getList(page); 
    model.addAttribute("paging", paging);   // 페이징된 보드를 view로 전달!
    
    return "board/list";    // templates/board/list.html 랜더링해서 리턴해라!
  }

 

 

/templates/board/list.html 수정
 // 수정 전
 <tr th:each="board, loop: ${boardList}">
 
 // 수정 후
 <tr th:each="board, loop: ${paging}">

 

 

실행

  • 실행시키면 위와 같은 코드가 콘솔에 찍힌다.
  • 해당 쿼리를 자동으로 생성해주는 것
-- Oracle 전용(11g 이하는 이 쿼리가 동작 안함)
select b1_0.bno,b1_0.content,b1_0.create_date,b1_0.title 
from board b1_0 offset 0 	-- 0부터 시작해서 페이지 사이즈만큼 증가
rows fetch first 10 rows only	-- 페이지사이즈

  • H2 Database에서 쿼리문을 실행시키면 10행씩 나오는 것을 확인할 수 있음!!

 

Bootstrap 적용시키기

/templates/board/list.html

<!-- 페이징 시작 -->
      <div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
          <!-- 이전버튼 -->
          <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
            <a th:href="@{|?page=0|}" class="page-link">《</a>
          </li>
          <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.number-1}|}" class="page-link">〈</a>
          </li>
          <!-- 페이지번호버튼 -->
          <li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}" 
              th:if="${page >= paging.number-5 and page <= paging.number+5}"
              th:classappend="${page == paging.number} ? active" class="page-item">
            <a th:href="@{|?page=${page}|}" th:text="${page + 1}" class="page-link"></a>
          </li>
          <!-- 다음버튼 -->
          <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.number+1}|}" class="page-link">〉</a>
          </li>
          <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.totalPages-1}|}" class="page-link">》</a>
          </li>
        </ul>
      </div>
<!-- 페이징 끝 -->

 

  • 적용된 것을 확인할 수 있음!
list.html 전체코드
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
    <div layout:fragment="main-content" class="container my-3">
      <!-- 게시글 리스트 -->
      <table class="table table-light table-striped">
        <thead class="table-dark">
          <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성일</th>
          </tr>
        </thead>
        <tbody>
          <!-- <tr th:each="board, loop: ${boardList}"> -->
            <tr th:each="board, loop: ${paging}">
              <td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
             <!-- <td th:text="${loop.count}"></td> -->
             <td>
              <a th:href="@{|/board/detail/${board.bno}|}" th:text="${board.title}"></a>
             </td>
            <td th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></td>
          </tr>
        </tbody>
      </table>
      <!-- 페이징 시작 -->
      <div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
          <!-- 이전버튼 -->
          <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
            <a th:href="@{|?page=0|}" class="page-link">《</a>
          </li>
          <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.number-1}|}" class="page-link">〈</a>
          </li>
          <!-- 페이지번호버튼 -->
          <li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}" 
              th:if="${page >= paging.number-5 and page <= paging.number+5}"
              th:classappend="${page == paging.number} ? active" class="page-item">
            <a th:href="@{|?page=${page}|}" th:text="${page + 1}" class="page-link"></a>
          </li>
          <!-- 다음버튼 -->
          <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.number+1}|}" class="page-link">〉</a>
          </li>
          <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
            <a th:href="@{|?page=${paging.totalPages-1}|}" class="page-link">》</a>
          </li>
        </ul>
      </div>
      <!-- 페이징 끝 -->
      <!-- 등록 버튼 -->
      <div class="d-flex justify-content-end">
        <a th:href="@{/board/create}" class="btn btn-sm btn-primary my-2">게시글 등록</a>
      </div>
    </div>
</html>
<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
  • paging.getTotalElements
    • 전체 게시물 개수 조회
  • paging.number
    • 현재 페이지 번호 조회
  • paging.size
    • 페이지당 게시물 개수 조회
  • loop.index
    • 나열 인덱스 조회(0부터 시작됨)