사이먼's 코딩노트

[SpringBoot] 내비게이션 바 / 페이징 본문

Java/SpringBoot

[SpringBoot] 내비게이션 바 / 페이징

simonpark817 2024. 5. 13. 10:53

[내비게이션 바 추가]

  • 지금까지 만든 SBB 프로그램은 질문 목록, 질문 상세 내용, 질문 등록, 답변 등록, 답변 목록 등 기능이 구현되어 있다.
  • 이제부터는 사용자가 해당 서비스를 좀 더 편리하게 사용할 수 있도록 다양한 기능을 구현해봅시다.
  • 먼저, 어떤 화면이 표출이되도 항상 메인 화면으로 돌아갈 수 있도록 내비게이션 바를 만들어 화면 상단에 고정해봅시다.
  • 내비게이션 바는 모든 화면 위쪽에 고정되어 있는 현재 적용된 부트스트랩의 컴포넌트 중 하나이다.
  • 모든 화면에 적용되어야 하기 때문에 공통 템플릿인 layout.html에 코드를 추가하는 것이 좋다.
  • 아래는 layout.html에 추가된 내비게이션 바 코드이다. 해당 코드는 부트스트랩에서 제공하는 html 코드를 그대로 참조하여 사용하였다.
<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
    <!-- sbb CSS -->
    <link rel="stylesheet" type="text/css" th:href="@{/style.css}">
    <title>Hello, sbb!</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">SBB</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="#">로그인</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
<th:block layout:fragment="content"></th:block>
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>
  • body 태그 안에서도 가장 상단에 nav 태그를 이용한 내비게이션 바 코드를 추가하였다.
  • 이 내비게이션 바에는 기능이 숨겨져 있는데, 웹 브라우저의 가로 사이즈를 마우스를 이용하여 점점 줄여보면 화면 오른쪽에 햄버거 메뉴 버튼이 생긴다.
  • 이와 같이 부트스트랩은 브라우저의 크기가 작아지면 자동으로 내비게이션 바에 있는 링크들을 햄버거 메뉴 버튼으로 숨기는 기능을 가지고있다.
  • 아래는 실제로 코드를 추가하고 로컬 서버를 다시 실행했을 때 화면 가장 상단에 나타나는 내비게이션 바의 모습이다.

내비게이션 바 추가

 

[공통 템플릿을 이용한 내비게이션 바 관리]

  • 유효성 체크때와 비슷하게 내비게이션 바도 공통 템플릿을 활용하여 관리해봅시다.
  • 먼저 내비게이션 바를 활용하기 위한 공통 템플릿으로 navbar.html 템플릿을 생성하여 아래와 같이 코드를 작성해봅시다.
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">SBB</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="#">로그인</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
  • 상위 nav 태그에 th:fragment="navbarFragment" 속성을 추가하여 다른 템플릿에서 해당 영역을 사용할 수 있도록 설정하였다.
  • layout.html 템플릿에 해당 부모 템플릿을 상속받으려면 내비게이션 바가 출력되는 태그 자리에 아래의 코드를 대체하면된다.
<!-- 네비게이션 바 -->
<nav th:replace="~{navbar :: navbarFragment}"></nav>

 

 

[페이징 기능 추가]

  • 이번에는 질문 목록이 여러 개가 있을 때 한 페이지에 모두 조회되는 것이 아닌 페이징 기능을 적용하여 입력된 정보나 데이터를 여러 페이지에 나눠 표시하고 사용자가 페이지를 이동할 수 있게 해봅시다.
  • 먼저 페이징 기능을 추가하기 전, 대량의 테스트 데이터를 만들기 위해 SbbApplicationTests.java 클래스에 아래와 같은 코드를 추가하고 실행시켜봅시다.
@Test
@DisplayName("대량 테스트 데이터 만들기")
void t012() {
    for(int i = 1; i <= 300; i++) {
        String subject = String.format("테스트 데이터입니다:[%03d]", i);
        String content = "내용무";
        this.questionService.create(subject, content);
    }
}
  • 위 코드와 같이 300개의 테스트 데이터를 만들고 로컬 서버를 다시 실행하면 아래 사진과 같이 대량의 데이터가 생성된 것을 확인할 수 있고, 그와 동시에 300개 이상의 데이터가 한 페이지에 보여지는 것을 확인할 수 있다.

대량 테스트 데이터 생성

 

  • 위와 같이 300개가 넘는 데이터를 확인하려면 계속 스크롤을 내려야한다.
  • 이러한 불편함을 해결하기 위해 페이징 기능을 이제부터 본격적으로 구현해봅시다.
  • 페이징을 구현하기 위해 부트스트랩 프레임워크를 사용할 예정이고, 추가로 설치해야하는 라이브러리는 없다.
  • JPA 환경 구축 시 설치했던 JPA 관련 라이브러리에 이미 페이징을 위한 패키지들이 포함되어 있고, 아래 클래스들을 이용하면 페이징을 쉽게 구현할 수 있다.
  • org.springframework.data.domain.Page : 페이징을 위한 클래스
  • org.springframework.data.domain.PageReequest : 현재 페이지와 한 페이지에 보여줄 게시물 개수 등을 설정하여 페이징요청을 하는 클래스
  • org.springframework.data.domain.Pageable : 페이징을 처리하는 인터페이스
  • 위에서 소개한 3가지 클래스를 사용하여 페이징을 구현해봅시다. 먼저, QuestionRepository.java에 아래와 같이 코드를 추가해야한다.
package com.sbs.sbb.Question;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer> {
    Question findBySubject(String subject);
    Question findBySubjectAndContent(String s, String s1);
    Page<Question> findAll(Pageable pageable);
}
  • Pageable 객체를 받아 Page<Question> 타입을 return하는 findAll() 메서드를 생성하였다.

 

[QuestionService 수정]

  • 다음은 QuestionService.java 클래스에 아래와 같이 코드를 추가해야한다.
public Page<Question> getList(int page) {
    Pageable pageable = PageRequest.of(page, 10);

    return this.questionRepository.findAll(pageable);
}
  • 질문 목록을 조회하는 getList() 메서드를 위와 같이 변경한다.
  • getList() 메서드는 정수 타입의 페이지 번호를 입력받아 해당 페이지의 Page 객체를 return하도록 하였다.
  • Pageable 객체를 생성할 때 사용한 PageRequest.of(page, 10)에서 page는조회할 페이지의 번호이고, 10은 한 페이지에 보여 줄 게시물의 개수를 의미한다. 이렇게 하면 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경된다.

 

[QuestionController 수정]

  • getList() 메서드의 입출력 구조가 변경되었기 때문에 아래와 같이 QuestionController.java 클래스에도 list() 메서드의 코드 수정이 필요하다.
@GetMapping("/list")
public String list(Model model, @RequestParam(value="page", defaultValue="0") int page) {
    // URL : localhost:8090/question/list 뒤에 ?page=0 이 붙는다.
    Page<Question> paging = this.questionService.getList(page);
    model.addAttribute("paging", paging);

    return "question_list";
}
  • GET 방식으로 요청된 URL에서 page값을 가져오기 위해 list() 메서드의 매개변수로 @RequestParam(value="page", defaultValue="0") int page를 추가하였다.
  • 이렇게 되면 각 페이지 별로 localhost:8090/question/list URL 뒤에 ?page=0의 형식이 붙게된다.
  • 스프링부트의 페이징 기능을 구현할 때는 첫 페이지 번호는 1이 아닌 0이므로 기본값으로 0을 설정해야한다.
  • GET 방식에서는 값을 전달하기 위해 ?와 &를 사용하고, 첫번째 파라미터는 ?를 사용하고 그 이후에 추가되는 값은 &을 사용한다.

 

[question_list.html 템플릿 수정]

  • 이제는 QuestionController.java 클래스에서 model 객체에 기존에 전달했던 이름인 'questionList'가 아닌 paging으로 질문 목록을 전달하기 때문에 question_list.html 템플릿에서도 아래와 같이 코드 수정이 필요하다.
<tbody>
    <tr th:each="question, loop : ${paging}">
        <td th:text="${loop.count}"></td>
        <td>
            <a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
        </td>
        <td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
    </tr>
</tbody>
  • th:each를 통한 반복문에서 questionList가 아닌 paging으로 수정하면 된다.
  • 수정을 마치고 브라우저에서 localhost:8090/question/list/?page=0이라는 URL을 요청하면 아래와 같이 첫 페이지에 해당하는 게시물 10개만 조회되는 것을 확인할 수 있다.

페이징 기능 추가

 

[페이지네이션 추가]

  • 이번에는 질문 목록에서 페이지를 이동하려면 페이지를 이동할 수 있는 '이전', '다음'과 같은 링크를 추가해봅시다.
  • 해당 기능을 위해서 부트스트랩의 페이지네이션 컴포넌트를 사용할 예정이다.
  • 아래는 question_list.html 템플릿에 추가된 페이지네이션 코드이다.
!-- 페이징 시작 -->
<nav th:if="${!paging.isEmpty()}">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${paging.number == 0} ? 'disabled'">
            <a class="page-link" th:href="@{|?page=0|}">
                <span>&laquo;</span>
            </a>
        </li>

        <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
            <a class="page-link" th:href="@{|?page=${paging.number-1}|}">Previous</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:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
        </li>

        <li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
            <a class="page-link" th:href="@{|?page=${paging.number+1}|}">Next</a>
        </li>

        <li class="page-item" th:classappend="${paging.number == paging.totalPages - 1} ? 'disabled'">
            <a class="page-link" th:href="@{|?page=${paging.totalPages - 1}|}">
                <span>&raquo;</span>
            </a>
        </li>
    </ul>
</nav>
<!-- 페이징 끝-->
  • 템플릿에 작성된 pagination, page-item, page-link 등이 페이지네이션 컴포넌트의 클래스로, 페이지네이션은 ul 요소 안에 있는내용을 꾸밀수 있고, page-item은 각 페이지 번호나 '이전', '다음' 버튼을 나타내도록 하고, page-link는 '이전', '다음' 버튼에 링크를 나타낸다.
  • 이전 페이지가 없는 경우 즉, 페이지 번호가 0일 때는 'Previous' 링크가 비활성화(disabled)되도록 하였고, 다음 페이지가 없을 경우에도 'Next' 링크가 비활성화되도록 하였다.
  • th:each 속성을 사용해서 전체 페이지 수만큼 반복하면서 해당 페이지로 이동할 수 있는 링크를 생성하였고, 이 때 반복하던 도중 요청 페이지가 현재 페이지와 같은 경우에는 active 클래스를 적용하여 페이지 링크에 파란색 배경이 나타나도록 하였다.
  • th:if 속성을 사용해서 조건을 통해서 page >= paging.number-5 and page <= paging.number+5 라는 코드를 추가하면 현재 페이지 기준으로 좌우 5개씩 페이지 번호가 표시되도록 한다.
  • 모든 코드 작성이 완료가 되었다면 로컬 서버를 재시작하고 질문 목록 페이지를 열였을 때 페이징과 페이지네이션이 제대로 적용되는 지 아래 사진과 같이 확인할 수 있다.

페이지네이션 추가

 

반응형