본문 바로가기

SpringBoot

[Do it] 3장 스프링 부트 고급 기능 익히기(1)

1. 내비게이션 바 추가

 

1) 내비게이션 바 만들기 

 

내비게이션 바 

- 모든 화면 위쪽에 고정되어 있는 부트스트랩의 컴포넌트 중 하나 

 

templates/layout.html

- <li>태그를 활용하여 내비게이션 바에 메뉴 추가 가능 

...
<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>
...

 

 

 

2) 햄버거 메뉴 

 

- 브라우저의 가로 사이즈를 마우스를 이용해 점점 줄이면 햄버거 메뉴 버튼이 생김 

- 로그인 링크는 사라짐 

- 부트스트랩은 브라우저의 크기가 작아지면 자동으로 내비게이션 바에 있는 링크들을 햄버거 메뉴 버튼으로 숨김

 

- 햄버거 메뉴 버튼을 활용할 수 있도록 부트스트랩 자바스크립트 파일(bootstrap.map.js)을 static 디렉터리로 복사 

 

- 자바스크립트 파일을 사용할 수 있도록 layout.html 수정 

 

 

 

 

 

3) 내비게이션 바 분리하기 

 

- 내비게이션 바도 공통 템플릿으로 활용 

 

templates/navbar.html

 

- layout.html 수정 

- 기존의 내비게이션 바 HTML 코드들을 삭제하고 navbar.html 템플릿을 타임리프의 th:replace 속성으로 layout.html 템플릿에 포함시킴 

 

templates/layout.html 

 

 

 

2. 페이징 기능 추가 

 

- 질문 목록 화면에 페이징 기능 적용

 

페이징(paging)

- 입력된 정보나 데이터를 여러 페이지에 나눠 표시하고, 사용자가 페이지를 이동할 수 있게 하는 기능

 

1) 대량 테스트 데이터 만들기 

 

- 스프링 부트의 테스트 프레임워크 이용 

 

SbbApplicationTests.java

- 총 300개의 테스트 데이터를 생성하는 테스트 케이스 작성 

package com.example.demo;

import com.example.demo.question.QuestionService;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SbbApplicationTests {
	
	@Autowired
	private QuestionService questionService;
	
	@Test
	void testJpa() {
		for(int i=0; i<=300; i++) {
			String subject = String.format("테스트 데이터입니다.:[%03d]",i);
			String content = "내용 없음";
			this.questionService.create(subject,content);
		}
	}
}

 

- 로컬 서버를 중지하고 [Run->Run As->Junit Test]로 testJpa 메서드 실행 

 

 

2) 페이징 구현하기 

 

org.springframework.data.domain.Page 페이징을 위한 클래스
org.springframework.data.domain.PageRequest 현재 페이지와 한 페이지에 보여 줄 게시물 수 등을 설정하여 페이징을 요청하는 클래스 
org.springframework.data.domain.Pageable 페이징을 처리하는 인터페이스 

 

 

QuestionRepository.java

package com.example.demo.question;

import java.util.List;

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 subject, String content);
	List<Question> findBySubjectLike(String subject);
	Page<Question> findAll(Pageable pageable);
}

 

- Pageable 객체를 입력받아 Page<Question> 타입 객체를 리턴하는 findAll 메서드 생성 

 

QuestionSerivce.java

package com.example.demo.question;
...

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;


@RequiredArgsConstructor
@Service
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public Page<Question> getList(int page) {
		Pageable pageable = PageRequest.of(page, 10);
		return this.questionRepository.findAll(pageable);
	}
...

 

- getList 메서드는 정수 타입의 페이지 번호를 입력받아 해당 페이지의 Page 객체를 리턴하도록 변경

- PageRequest.of(page, 10)에서 page는 조회할 페이지의 번호이고 10은 한 페이지에 보여 줄 게시물의 개수 

- 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리 변경 

 

 

 QuestionController 변경 

 

QuestionController.java

...
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.data.domain.Page;

@RequestMapping("/question")
@RequiredArgsConstructor
@Controller
public class QuestionController{
	
	private final QuestionService questionService;
	
	@GetMapping("/list")
	public String list(Model model, @RequestParam(value="page", defaultValue="0")int page) {
		Page<Question> paging = this.questionService.getList(page);
		model.addAttribute("paging", paging);
		return "question_list";
	}
 ...

 

- http://localhost:8080/question/list?page=0와 같이 GET 방식으로 요청된 URL에서 page값을 가져오기 위함

- list 메서드의 매개변수로 @RequestParam(value="page", defaultValue="0") int page 추가 

- URL에 매개변수로 page가 전달되지 않은 경우 기본값은 0으로 되도록 설정

 

 

질문 목록 템플릿 변경 

 

- 컨트롤러에서 model 객체에 기존에 전달했던 이름인 'questionList' 대신 'paging'으로 전달하기 때문에 question_list.html 변경

 

templates/question_list.html

 

 

속성 설명
paging.isEmpty 페이지의 존재 여부
paging.totalElements 전체 게시물 개수
paging.totalPages 전체 페이지 개수
paging.size 페이지당 보여줄 게시물 개수
paging.number 현재 페이지 번호
paging.hasPrevious 이전 페이지의 존재 여부
paging.hasNext 다음 페이지의 존재 여부 

 

 

3) 페이지 이동 기능 추가하기 

 

- 질문 목록 화면에서 페이지를 이동할 수 있는 링크 추가 

 

templates/question_list.html

...
<!-- 페이징처리 시작 -->
    <div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
                <a class="page-link"
                    th:href="@{|?page=${paging.number-1}|}">
                    <span>이전</span>
                </a>
            </li>
            <li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
                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}|}">
                    <span>다음</span>
                </a>
            </li>
        </ul>
    </div>
    <!-- 페이징처리 끝 -->
	<a th:href="@{/question/create}" class="btn btn-primary">질문 등록하기</a>
</div>

 

- pagination은 ui 요소 안에 있는 내용을 꾸밀 수 있고, page-item은 각 페이지 번호나 '이전', '다음' 버튼을 나타내도록 하고, page-link는 '이전','다음' 버튼에 링크를 나타냄 

 

- 이전 페이지가 없는 경우에는 '이전' 링크가 비활성화 되도록 함

- th:each 속성을 사용해 전체 페이지 수만큼 반복하면서 해당 페이지로 이동할 수 있는 '이전', '다음' 링크를 생성

 

 

4) 페이지 이동 기능 완성하기 

 

- 페이지 표시 제한 기능 구현 

- 현재 페이지 기준으로 좌우 5개씩 페이지 번호가 표시되도록 만듬 

 

templates/question_list.html

 

 

 

5) 최신순으로 데이터 조회하기 

 

QuestionService.java

package com.example.demo.question;

import java.util.ArrayList;
import java.util.List;
...
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@RequiredArgsConstructor
@Service
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public Page<Question> getList(int page) {
		List<Sort.Order>sorts = new ArrayList<>();
		sorts.add(Sort.Order.desc("createDate"));
		Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
		return this.questionRepository.findAll(pageable);
	}
	
...

 

- 게시물을 역순(최신순)으로 조회하려면 PageRequest.of 메서드의 세 번째 매개변수에 Sort 객체를 전달해야 함 

- 작성 일시(createDate)를 역순(Desc)으로 조회하려면 Sort.Order.desc("createDate")와 같이 작성