반응형

Spring Boot 컨트롤러


스프링 부트에서 먼저 페이징 처리 코드를 살펴 보도록 하겠습니다.

컨트롤러

  • @PageableDefault : 페이징 파라미터가 없을 경우 디폴트 값 입니다.
    page 파라미터의 경우 디폴트가 0이러서 꼭 명시할 필요는 없습니다.
  • Spring Boot Jpa를 사용하여 Pageable로 직접 구현없이 페이징을 하고 싶다면 직접 jpql이나 네이티브 쿼리 등
    커스텀 하여 사용하면 Srping Data Jpa의 페이징 기능을 사용할 수 없게 됩니다.
    물론 Query Dsl을 사용해도 되고, 다른 방법도 있겠지만, 딱히 어렵게 할 필요가 없었기 때문에
    jpql로 직접 쿼리를 작성 하였다가 쿼리 메서드르 변경이 가능한 쿼리여서 쿼리 메서드를 사용하여
    페이징 기능을 사용할 수 있게 되었습니다.
  • @RequiredArgsConstructor @Controller public class BlogController { private final BlogDetailRepository blogDetailRepository; @ResponseBody @GetMapping("/") public Page<BlogDetail> blogs(Model model, @PageableDefault(page = 0, size = 2, direction = Sort.Direction.DESC) Pageable pageable) { Page<BlogDetail> blogDetails = blogDetailRepository.findAllByOrderByPubDateDesc(pageable); return blogDetails; }

Pageable
Pageable은 인터페이스로써 PageRequest라는 구현체를 주입 받게 된다고 하네요.
아직 직접 뜯어보지는 않아서 자세히는 모르겠습니다 ㅜ.ㅜ

Api 요청 시 리턴 값

대략 이정도의 정보만 알면 페이징은 사용할 수 있습니다.
index는 0부터 시작 합니다.

  • content : 컨트롤러에서 엔티티, Dto등 매핑된 데이터
  • pageable.pageNumber, number : 현재 페이지 index( 내가 보고자 하는 페이지 )
  • pageable.pageSize, size : 페이지 당 보여줄 글의 개수
  • totalPages : 전체 페이수
  • totalElements : 전체 글의 개수
  • first : 현재 페이지의 첫 페이지 여부
  • last : 현재 페이지의 끝 페이지 여부

Thymeleaf에서 사용하기


먼저 Api 방식이 아닌 템플릿 방식으로 컨트롤러를 변경 합니다.

@RequiredArgsConstructor
@Controller
public class BlogController {

    private final BlogDetailRepository blogDetailRepository;

//    @ResponseBody
    @GetMapping("/")
    public String blogs(Model model, @PageableDefault(page = 0, size = 2, direction = Sort.Direction.DESC) Pageable pageable) {
//    public Page<BlogDetail> blogs(Model model, @PageableDefault(page = 0, size = 10, direction = Sort.Direction.DESC) Pageable pageable) {
        Page<BlogDetail> blogDetails = blogDetailRepository.findAllByOrderByPubDateDesc(pageable);
//                .stream()
//                .map(BlogDetailDto::new)
//                .collect(Collectors.toList());

        model.addAttribute("blogDetails", blogDetails);

        return "blogList";
//        return blogDetails;
    }
}

Thymeleaf 소스 분석

소스를 보면 기능 단위로 주석을 만들어 놓았다. 주석 단위로 소스를 분석 해보도록 하겠습니다.
프로그래밍을 하다 보면, index가 0부터 시작해서 헷갈리는 경우가 많은데
그 부분을 해결 하려다가 오히려 더 복잡해져서 그냥 그대로 사용 했습니다.
index를 1로 세팅하기 위해 paging 리턴 값에 + 1을 더해서 전역 변수로 사용 해보고 했는데..
오히려 더 헷갈리더라구요. 그래서 그대로 사용하기로 했고, 사용하는 측에서 웬만하면 연산을 하는 코드로 작성 했습니다.
참고 사이트 1
참고 사이트 2

  <!-- 페이징 -->
  <div th:if="${!blogDetails.isEmpty()}">
    <!-- 전역 변수 선언 -->
    <nav
            th:with="
                pageNumber = ${blogDetails.pageable.pageNumber},
                pageSize = ${blogDetails.pageable.pageSize},
                totalPages = ${blogDetails.totalPages},
                startPage = ${T(Math).floor(pageNumber / pageSize) * pageSize + 1},
                tempEndPage = ${startPage + pageSize - 1},
                endPage = (${tempEndPage < totalPages ? tempEndPage : totalPages})"
            aria-label="Page navigation"
    >
      <ul class="pagination ">
        <!-- 처음으로 이동 -->
        <li th:classappend="${pageNumber < pageSize} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="@{/(page=0)}">
            <span>&laquo;</span>
            <span class="sr-only">First</span>
          </a>
        </li>

        <!-- 이전으로 이동 -->
        <li th:classappend="${blogDetails.first} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="${blogDetails.first} ? '#' : @{/(page=${pageNumber - 1})}" aria-label="Previous">
            <span aria-hidden="true">&lt;</span>
            <span class="sr-only">Previous</span>
          </a>
        </li>

        <!-- 특정 페이지로 이동 -->
        <li th:each="page: ${#numbers.sequence(startPage, endPage)}" th:classappend="${page == pageNumber + 1} ? 'active'" class="page-item">
          <a th:text="${page}" class="page-link" th:href="@{/(page=${page - 1})}"></a>
        </li>

        <!-- 다음으로 이동 -->
        <li th:classappend="${blogDetails.last} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="${blogDetails.last} ? '#' : @{/(page=${pageNumber + 1})}" aria-label="Next">
            <span aria-hidden="true">&gt;</span>
            <span class="sr-only">Next</span>
          </a>
        </li>

        <!-- 마지막으로 이동 -->
        <li th:classappend=" ${T(Math).floor(totalPages / pageSize) * pageSize - 1 <= startPage} ? 'disabled'" class="page-item">
          <a class="page-link" th:href="@{/(page=${totalPages - 1})}">
            <span>&raquo;</span>
            <span class="sr-only">Last</span>
          </a>
        </li>
      </ul>
    </nav>
  </div> <!-- /container -->

이제 주석 단위로 설명 하겠습니다.
위에 Api 결과 값을 참고해 주시기 바랍니다.
전역 변수 선언

  • stratPage : + 1을 한 이유는 사용자 눈에 보이는 페이지는 0페이지 부터가 아닌 1페이지 부터이기 때문에 +1을 했습니다.
  • tmpEndPage : 단순히 endPage 연산에서 사용하기 쉽게 만들어 놓은 변수 입니다.
             현재 페이지의 끝 페이지 번호를 구하는 변수 입니다.
  • endPage : 현재 페이지의 끝 번호가 전체 페이지 번호 보다 작으면 아직 끝 페이지 라인에 온 것이 아니기 때문에
          현재 페이지의 끝 번호를 endPage로 지정 하고,  
          현재 페이지의 끝 번호가 전체 페이지 번호 이상이 나오면 안되기 때문에 전체 페이지 번호를 끝 번호로 사용 합니다.

처음으로 이동

  • 현재 페이지의 인덱스(0부터 시작)가 페이지 사이즈 보다 작을 경우 아직 첫 페이지 라인에 머물로 있는 것이므로,
    처음으로 이동 버튼을 비활성화 시킵니다.

이전으로 이동

  • 첫 페이지라면 이전으로 이동 버튼을 비활성화 하고 href 링크도 없는데요. 첫 페이지는 이전이 없기 때문입니다.
    그리고 첫 페이지가 아니라면, 현재 페이지 인덱스(0부터 시작) - 1을 하면 이전으로 가겠죠?

특정 페이지로 이동

  • 내가 클릭한 페이지 번호로 이동하는 소스 입니다.
    startPage 부터 endPage까지 반복문을 돌리는 소스 입니다. 즉, startPage 1 부터 시작하는 것 입니다.
    그 값은 page라는 변수에 담겨 사용 됩니다.
  • page와 현재 페이지(인덱스 0 부터) + 1이 같다면 버튼을 찐하게 표시해 줍니다.
    현재 페이지를 표시하기 위함이죠.
    href 링크에 - 1을 한 이유는 page는 1부터 시작하기 때문에 실제 페이지 값은 인덱스 0부터 시작하는 현상 때문 입니다.

다음으로 이동

  • 처음으로 이동과 동일하고, frist 대신 last를 체크, - 1 대신 + 1을 해줍니다.

마지막으로 이동

  • startPage가 앞의 연산 결과 이상 이라면 마지막 페이지 라인에 위치한 상태로 마지막 페이지로 이동할 필요 없이 직접 마지막 페이지 번호를
    누르면 되기 때문에 disable 처리합니다.
    그게 아니라면, 전체 페이지 수 - 1만큼 이동합니다. 전체 페이지 수는 index가 0 부터 시작하기 때문에 - 1이 필요 합니다.
반응형
복사했습니다!