본문 바로가기
Project/🥑JPA + SpringSecurity 게시판

[JPA + SpringSecurity 게시판] #10 Pageable 페이징 처리

by 로띠 2022. 5. 31.

 

 

짝짝짝 👏

이전글에서 🥑 CRUD 🥑 를 모두 완성했다!

이제 좀 더 게시판스러움을 더하기 위해

JPAPageable을 이용한 아주 간단한 페이징을 추가해보자


 

 

 

JPA의Pageable’로 페이징 기능을 구현해보자

 

PageableJpaRepository를 상속했을 때 제공되는 페이징 기능
더보기
Pageable 보기
public interface Pageable {

	/**
	 * Returns a {@link Pageable} instance representing no pagination setup.
	 *
	 * @return
	 */
	static Pageable unpaged() {
		return Unpaged.INSTANCE;
	}

	/**
	 * Creates a new {@link Pageable} for the first page (page number {@code 0}) given {@code pageSize} .
	 *
	 * @param pageSize the size of the page to be returned, must be greater than 0.
	 * @return a new {@link Pageable}.
	 * @since 2.5
	 */
	static Pageable ofSize(int pageSize) {
		return PageRequest.of(0, pageSize);
	}

	/**
	 * Returns whether the current {@link Pageable} contains pagination information.
	 *
	 * @return
	 */
	default boolean isPaged() {
		return true;
	}

	/**
	 * Returns whether the current {@link Pageable} does not contain pagination information.
	 *
	 * @return
	 */
	default boolean isUnpaged() {
		return !isPaged();
	}

	/**
	 * Returns the page to be returned.
	 *
	 * @return the page to be returned.
	 */
	int getPageNumber();

	/**
	 * Returns the number of items to be returned.
	 *
	 * @return the number of items of that page
	 */
	int getPageSize();

	/**
	 * Returns the offset to be taken according to the underlying page and page size.
	 *
	 * @return the offset to be taken
	 */
	long getOffset();

	/**
	 * Returns the sorting parameters.
	 *
	 * @return
	 */
	Sort getSort();

	/**
	 * Returns the current {@link Sort} or the given one if the current one is unsorted.
	 *
	 * @param sort must not be {@literal null}.
	 * @return
	 */
	default Sort getSortOr(Sort sort) {

		Assert.notNull(sort, "Fallback Sort must not be null!");

		return getSort().isSorted() ? getSort() : sort;
	}

	/**
	 * Returns the {@link Pageable} requesting the next {@link Page}.
	 *
	 * @return
	 */
	Pageable next();

	/**
	 * Returns the previous {@link Pageable} or the first {@link Pageable} if the current one already is the first one.
	 *
	 * @return
	 */
	Pageable previousOrFirst();

	/**
	 * Returns the {@link Pageable} requesting the first page.
	 *
	 * @return
	 */
	Pageable first();

	/**
	 * Creates a new {@link Pageable} with {@code pageNumber} applied.
	 *
	 * @param pageNumber
	 * @return a new {@link PageRequest}.
	 * @since 2.5
	 */
	Pageable withPage(int pageNumber);

	/**
	 * Returns whether there's a previous {@link Pageable} we can access from the current one. Will return
	 * {@literal false} in case the current {@link Pageable} already refers to the first page.
	 *
	 * @return
	 */
	boolean hasPrevious();

	/**
	 * Returns an {@link Optional} so that it can easily be mapped on.
	 *
	 * @return
	 */
	default Optional<Pageable> toOptional() {
		return isUnpaged() ? Optional.empty() : Optional.of(this);
	}

}

 

 

  1. 인터페이스 구현체를 보면 다양한 기능이 제공되는데
  2. 현재 페이지, 전체 페이지, 정렬

 

  1. ABoardcadoint getPageNumber();
  2. 현재 페이지 번호를 기준으로 구현했다. 다양한 기능만큼 다양한 방법으로 구현할 수 있는데 최대한 쉽게 가보자
  3. 그리고 앞, 뒤 버튼 사용을 결정하는 맨 앞, 뒤 페이지를 체크할 수 있는 요소 등이 보인다

 

  1. Pageable을 이용할 경우 Page<board> 로 반환 받게되는데 이는 Repository를 통해 받는 Page<Entity>
  2. Page 타입은 직접 사용할 수 없어서 List 로 변환하여야한다 ❗
  3. Controller에서 Pageable 객체에 쿼리 파라미터를 바인딩해 실행 http://localhost:8088/?page=0

 

 

코딩시작 👩🏻‍💻

기존 class에 아래 코드를 수정, 추가

 

 

 

1. Service

 

✅ 페이징 구현을 위해 List 메소드() 수정

 

 

[ BoardService ]

- getBoardList() 기존 코드
@Transactional
    public List<Board> getBoardList() {
        List<Board> boardList = boardRepository.findAll(Sort.by(Sort.Direction.DESC, "id", "createdDate"));
        
return boardList;

⬇️

[ BoardService ]

- getBoardList() 수정
@Transactional(readOnly=true)
    public List<BoardDto> getBoardList(Pageable pageable) {
        Page<Board> boards = boardRepository.findAll(pageable);
        List<Board> boardList = new ArrayList<>();

        for (Board board : boards) {
            boardList.add(board);
        }
        return boardList;
    }

 

✅ Pageable

 

  • JPA Repository를 상속하여 Pageable 이용
  • JPA의 반환타입중 하나
    • List<Board>가 아닌 Page<Board>로 받음
      • 1,2,3페이지처럼 페이지 당 글 갯수를 정하면 페이지를 나누어 반환해줌
      • 위 사진은 bootstrap까지 결합한 것 (아래 설명)

 

 


 

2. Controller

 

 

Controller의 파라미터에 Pageable객체 설정

요청이 왔을 때 내부적으로 resolver가 동작해 Pageable에 쿼리 파라미터를 넣어 실행

  • http://localhost:8088/?page=0

 

view인 Thymeleaf 에서도 계산식을 적용할 수 있고

Page<>를 불러 쓰는 방식도 있지만

  • th:href="@{/(page=${boardList.pageable.pageNumber -1})}"

 

웬만하면 사용할 값만 서버단에서 완성된 값으로 넘기려고 Model에 담았다

 

 

[ BoardController ]- “/” List 매핑부분 변경추가아님

private final BoardRepository boardRepository;

@GetMapping("/")
public String boardList(@PageableDefault(page = 0, size = 5, sort = "id", direction = Sort.Direction.DESC) Pageable pageable, Model model) {

        Page<Board> boards = boardRepository.findAll(pageable);
        List<Board> boardList = boardService.getBoardList(pageable);

        int nowPage = boards.getPageable().getPageNumber()+1;
        int startPage = Math.max(nowPage-4,1);
        int endPage = Math.min(nowPage+5, boards.getTotalPages());
        int totalPages = boards.getTotalPages();

        model.addAttribute("boardList", boardList);
        model.addAttribute("nowPage", nowPage);
        model.addAttribute("startPage", startPage);
        model.addAttribute("endPage", endPage);
        model.addAttribute("totalPages", totalPages);

        return "board/list";
}

 

 @PageableDefault Pageable 기본 값 설정 (controller 단위)

  • http://localhost:8088/?page=0 쿼리 파라미터를 넘기지 않았을 때 실행될 기본 값
    • page : 불러올 페이지 번호
    • size : 한 페이지 당 불러올 게시글 수
    • sort : 정렬 기준
    • direction : 정렬 방향

 

 getPageable() 객체 인자 사용

  • getPageNumber() : 현재 페이지 번호 ( 0부터 시작하기 때문에 +1 )
  • getTotalPages : 총 페이지 수

 

 startPage 블럭에서 보여줄 시작 페이지

  • 음수가 되지 않도록 Math.max 설정

 

 endPage 블럭에서 보여줄 마지막 페이지

  • 총 게시글 페이지를 초과하지 않도록 Math.min 총 페이지 설정

 

 

 

 


 

 

3. list.html

 

[ list.html ] - </table> 아래 추가

<div class="page">
		<ul class="pagination justify-content-center pagination-green" >
		
			  <!-- 이전 페이지 버튼 -->
			  <li class="page-item" th:classappend="${1 == nowPage }? 'disabled'">
			      <a th:href="@{/(page=${nowPage -2})}" class="page-link">Prev</a>
			  </li>
			
			  <!-- 현재 페이지 및 블럭 -->
			  <li class="page-item" th:classappend="${page == nowPage }? 'active'" th:each="page : ${#numbers.sequence(startPage, endPage)}">
			      <a th:if="${page!=0}" a th:href="@{/(page=${page-1})}" class="page-link" th:text="${page}" >1</a>
			      </a>
			  </li>
			
			  <!-- 다음 페이지 버튼-->
			  <li class="page-item" th:classappend="${totalPages == nowPage }? 'disabled'">
			      <a th:href="@{/(page=${nowPage})}" class="page-link">Next</a>
			  </li>
		
		</ul>
</div>

 

 Bootstrap 의 < Pagination >

ul class 이름 <ul class=”pagination”>

li class 이름 <li class=”page-item”>

a class 이름 <a class=”page-link”> 으로 지정하면

 

Bootstrap에서 제공하는 페이징 툴이 그려진다

 

추가로 class에 active, ‘disabled’ 를 추가하면 강조 비활성화 가능

th:classappend="${1 == nowPage }? 'disabled'"

 

 

 

✅ th:classappend

  • th는 thymeleaf 문법. class에 이름을 추가할 수 있는 기능

 

 nowPage

getPageable().getPageNumber

Controller에서 Pageable의 기능으로 현재 페이지를 model에 담아 보낸 값

 

 { 조건 } ? True value

thymeleaf 조건식. True 일때 True value 실행

 

 ${1 == nowPage }? 'disabled'

  • nowPage가 1이면, 즉 1페이지라면 [ Prev ] 이전 페이지 버튼 비활성화
    • class 이름에 ‘disable’을 추가하면 비활성화 ( Bootstrap )

 ${page == nowPage }? 'active'

  • page값이 nowPage이면, 즉 페이지 블록 넘버 중 현재 페이지 버튼은 ‘active
    • class 이름에 ‘active를 추가하면 배경 컬러 활성화 ( Bootstrap )

 

 

 

th:each="page : ${#numbers.sequence(startPage, endPage)}"

 

 th:each = “page”

th는 thymeleaf 문법 표시. each는 for문을 돌려 배열을 생성

  • 위 코드의 배열명은 page
    • ${page} 로 사용

 

✅ ${#numbers.sequence(startPage, endPage)}

  • Thymeleaf의 Utility. 시퀀스 증가 반복 처리
  • ${#numbers.sequence( from, to, step )}
    • from : 증가 시작 점
      • startPage - Controller Model에 담은 블럭에서 보여줄 시작 페이지
    • to : 증가 종료 점
      • endPage - Controller Model에 담은 블럭에서 보여줄 마지막 페이지
    • step : 한 번에 증가할 양 ( default = 1 , 생략가능 )


  •  

 

th:href= 타임리프 주소 이동

 

 @{ /주소 }

  • 기본적으로 특정 URL주소 서버내 리소스 경로

 @{ /주소 (파라미터=${호출 할 서버 파라미터}) }

  • 파라미터 값을 주소에 추가할 때 → "@{/ (page=${nowPage}) }"

 

"@{/(page=${nowPage - 2})}"> Prev

  • 이전 페이지로 이동하는 [ Prev ] 버튼
  • nowPage- 1 이 아닌 이유
    • 이전 페이지라면 nowPage - 1이 맞다고 생각되지만
    • nowPage는 페이지네이션 버튼 넘버 기준이고
    • Pageable 0부터 시작이기 때문에
    • 쿼리 파라미터 값 ?page=0 일때 1페이지
      • 페이지 클릭 블록이 [ 0 1 2 3 ]으로 시작하면 이상하자나,, 🥴

 

✅"@{/(page=${page-1})} th:text="${page}"> 1

  • 클릭한 해당 페이지로 이동하는 [ 페이지 넘버 ] 버튼
  • nowPage를 사용하지 않은 이유
    • 현재 페이지는 ${#numbers.sequence(startPage, endPage)}로 생성
      • nowPage값이 없는 페이지까지 증가하게됏다,,,ㅠㅠ
  • page - 1 을 하는 이유
    • Prev 페이지에서 -2 한 이유와 동일 = 쿼리 파라미터값이 1 더 작기때문

 

 

 

 

🙊

WoW! 완성

 

 

5일이 걸렸던 Paging

누가보면 웃을지 몰라도 JPA를 공부함에 있어 값진 시간이였다 GOOD!!

 

다음편에선 🔍검색기능을 추가하겠다!