origoni's Blog from Millky

origoni의 스프링 블로그 입니다.

[블로그개발_13] 글 목록 페이징 및 정렬 (SpringData Pageable, Page)

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

라이브 데모 : http://blog.millky.com/post/list

자바 웹 개발 시작하기 : http://www.slideshare.net/origoni/presentations




이번 시간은 블로그 포스트 리스트 페이징 및 정렬 부분을 추가해 보자.

(추가적으로 부트스트랩 버전업 및 jspf 로 헤더 푸터 분리하기 등을 해 보겠다.)


라이브 데모 : http://blog.millky.com/post/list

오늘 작업 코드 : https://github.com/origoni/Spring-Blog/releases/tag/v0.0.13



이전에 페이징을 하려면.. 준비해야 할 것이 좀 있었다.

하지만 이제는 스프링 데이터를 사용하여 간단하게 이전 글목록 다음 글목록을 만들어보자.


우선 지금까지 사용하던 글 목록 가지고 오는 부분을 보자.

@RequestMapping("/list")
public String list(Model model) {
	List<Post> postList = postDao.findAll();
	model.addAttribute("postList", postList);
	...
	return "list";
}


postDao 에서 findAll() 해서 모든 글을 가지고 와서 list.jsp로 넘겨주게 되어있다.

글이 많아질수록 한번에 가지고 와야 하는 글이 ... 뭐 이런 당연한 이유는 그만 적고. 그럼 페이지 나누기는 어떻게 하면 될까?


지금까지는 아래와 같이 계산을 했었다.

http://www.slideshare.net/origoni/java-web-development10draft 14~15 페이지


사실 별것 아니지만. 하려면 매우 귀찮... 은 이런일은 스프링 데이터가 다 해준다.

이제 하나씩 해보자.




여기서 글을 가지고오는 핵심적인 부분인 .findAll() 은 이름만 봐서는 모든걸 다 가지고 오는것이긴 한데.. 혹시 추가적인 파라미터 받을 수 있는지 확인해보자.




확인해보니.. ids를 가지고 여러 글을 가지고 오는 방법도 있고. (우리가 오늘 사용할) Pageable 이라는 파라미터를 받아서 페이지 라고 돌려주는 방식도 있고. Sort를 넣어 정렬된 목록을 가지고 오는 방식도 있다.

우리는 페이징을 해야 하니. Pageable 을 사용해보자.

우선 설명을 따라 PagingAndSortingRepository 에 가 보았다.




Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.


그렇군. 단순한 말은 써 있는데 어떻게 써야 할 지 모르겠다.

음 어디서 뭐부터 봐야 하지?


우리는 지금 spring-data-jpa 를 사용하고 있으니. 해당 공식문서를 찾아보자.

http://docs.spring.io/spring-data/jpa/docs/1.7.2.RELEASE/reference/html/#_handlermethodargumentresolvers_for_pageable_and_sort


영어라고 어려워 할 필요는 없다. 코드만 봐도 기본적인 사용법을 바로 알 수 있다. (이렇게 말은 하지만... 문서 자체가 아주 길지 않으니 처음부터 한번 스윽 보면 많은 도움이 될 것이라 확신한다. 사실 모든걸 기억할 필요도 없다. 그냥 어떤 문제가 발생했을때. '아 전에 본 그 래퍼런스에 비슷한 이슈가 있었어!' 만 기억할 수 있어도. 다시 찾아보면 되니까. 나도 맨날 다시 찾아본다. 아직 '습'이 안되어서....)




Pageable 의 간단한 사용 예와 page, size, sort 라는 파라미터를 사용할 수 있게 되었다는것을 알았다.


위의 예를 참고로 우리 코드를 수정해 보자.


@RequestMapping("/list")
public String list(Model model, Pageable pageable) {
	Page<Post> postPage = postDao.findAll(pageable);
	model.addAttribute("postPage", postPage);
	...
	return "list";
}

우선 이렇게 바꾸었다.

Pageable 을 입력받게 되었고.

결과값이 Page라는 객체로 넘어온다. 뭐가 들었느지 좀 보니...




contnet, number, first, last 등이 사용 가능해 보인다.


그럼 이제 뷰도 수정해 보자.


<c:forEach var="post" items="${postList}">

부분을


<c:forEach var="post" items="${postPage.content}">

요렇게 바꾸었다.


버튼도 수정해야 하지만 그에 앞서... page, size, sort 라는 파라미터를 사용해서 동작하는지 확인해보자.

프로젝트를 돌리고.. 로그인헤서 글을... 한 5개 쓰고...


http://localhost:8080/post/list

목록을 보니.. 이렇다.

이제 페이지를 나누어 보자.. 1페이지에 글을 2개씩 보이도록!


http://localhost:8080/post/list?size=2

오 잘 보인다.. 그런 2페이지로?!


http://localhost:8080/post/list?size=2&page=2

오잉? 왜 3, 4번이 아니고 5번이지? ㅠ 사실 페이지 번호가 너무 개발자 스럽게 0이 첫페이지다. 아 우리는 개발자니까. 이상한것이 아니지?.. 그렇지.


http://localhost:8080/post/list?size=2&page=1

요렇게 하면. 우리가(?) 생각하는 2 페이지가 잘 나온다..



아 정렬도 해야 하는구나.. 최신글이 위로 나오는게 좋을 것 같다.


http://localhost:8080/post/list?size=2&page=0&sort=id,desc

이렇게 하면 한페이지에 2개의 글씩 첫페이지(0) 정렬은 id 로 역정렬(desc) 이 된다.

헐.. 1페이지 보려면 이렇게 파라미터가 많이 붙어야 한다고?!


그래서 @PageableDefault 라는것이 있다.



이렇게 기본값을 정해줄 수 있다.

최종적으로 컨트롤러를 이렇게 작성할 수 있다.


@RequestMapping("/list")
public String list(Model model,
		@PageableDefault(sort = { "id" }, direction = Direction.DESC, size = 2) Pageable pageable) {
	Page<Post> postPage = postDao.findAll(pageable);
	model.addAttribute("postPage", postPage);
	User user = getConnect();
	model.addAttribute("user", user);
	return "list";
}


이러면 우리가 원하는 한페이지에 2개씩 그리고 기본적으로 아이디 역정렬이다.


한번 돌려서 확인해보자.

음 힘겹게 글을 다시 3개 쓰고 확인해보니 잘 된다. (아 이 DB를 메모리 디비로 했더니 ㅠㅠ 디스크에 쓰게 하거나.. MySQL 같은걸 쓰거나. ㅠㅠ 아 class 핫 디플로잉!!.. 나중에 알아보자;;)





그럼 이제 앞뒤 버튼을 넣어보자.


이전에

<ul class="pager">
	<li class="next">
		<a href="#">Older Posts &rarr;</a>
	</li>
</ul>


템플릿이 요렇게 있었는데..

음. 이전 부분도 좀 만들어주지... 부트스트랩에 가서 확인해 봐야 겠다.


부트스트랩을 방문한다.




오...

버전업.. 이부분은 잠시 후에 보자.


일단은 좀 찾아보니..



이런 부분을 발견했다.

또 예에 나온것처럼 뷰를 수정해보자.


<ul class="pager">
	<li class="previous">
		<a href="#">Newer Posts &rarr;</a>
	</li>
	<li class="next">
		<a href="#">Older Posts &rarr;</a>
	</li>
</ul>

요렇게 수정하고 보니..




헉... &rarr; 이게 오른쪽 화살표라는 말이었구나 나도 오늘 알았다.

그럼 왼쪽 화살표는? &larr; 겠지?


그리고. 눌렀을때 링트도 있어야 하고...

버튼이 필요할때만 나와야 하는데?!


아까 위에서 본. first, last, number을 사용하면 된다.


최종적으로 이런 모습이 된다.


<ul class="pager">
	<c:if test="${!postPage.first}">
	<li class="previous">
		<a href="?page=${postPage.number-1}">&larr; Newer Posts</a>
	</li>
	</c:if>
	<c:if test="${!postPage.last}">
	<li class="next">
		<a href="?page=${postPage.number+1}">Older Posts &rarr;</a>
	</li>
	</c:if>
</ul>


뭐 간단하다. 처음이 아니면 앞으로 버튼을 보이고. 끝이 아니면 뒤로 버튼을 보인다.


돌려볼까?


잘 동작한다 ^^




오늘 부트스트랩 버전업도 할겸 UI부분을 좀 더 수정해 보자.


우선은... 수정 삭제 버튼을 글쓴이만 보이도록 해보자..


다행이 post.jsp에 이미 user 를 내려주고 있다.

user의 providerUserId 같과 post의 userId 값이 일치할때만. 버튼을 보여주면 되겠다.


<c:if test="${user!=null && user.providerUserId == post.userId}"> 
<div class="pull-right">
	<a href="/post/${post.id}/edit">
		<button type="button" class="btn btn-warning">Edit</button>
	</a>
	<a href="/post/${post.id}/delete" onclick="if(!confirm('진심이에요?')){return false;}">
		<button type="button" class="btn btn-danger">Delete</button>
	</a>
</div>
</c:if>


그리고.. 포스트 관련 jsp를 하나의 폴더에 좀 모아두려 한다.




이렇게 post라는 폴더를 만들어 form, list, post.jsp 파일을 옮기고..

컨트롤러에서 뷰 return에 post/ 를 붙이면 된다. return "post/form"; 요런식으로 말이다.



그리고 부트 스트랩 버전업을 해보자. 뭐 매우 간단한다.

pom.xml에서 3.3.4를 3.3.5바꾸고.

<dependency>
	<groupId>org.webjars.bower</groupId>
	<artifactId>bootstrap</artifactId>
	<version>3.3.5</version>
</dependency>


각각의 jsp파일에서.. 

역시 3.3.4 부분으로 3.3.5로 변경하면 된다.

/webjars/bootstrap/3.3.5/dist/css/bootstrap.min.css


음 그런데 jsp파일이 많네..

워 이클립스에서 일괄 파일 수정하면 된다.

하지만 서버에 올라간 파일을 긴급하게 수정해야 한다면!!

아 뭐 이런 극한 상황이 아니더라도. 헤더 푸터는 좀 나누어 보면 한다.


여기서 jspf 라는 파일을 사용할 것인데. 뭐 특별한 것은 없고 jsp조각이라는 뜻이다.


WEB-INF 아래 jspf 라는 폴더를 만들고.

각각의 jspf파일을 만들어보자.


head.jspf

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/webjars/bootstrap/3.3.5/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/webjars/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/webjars/origoni-startbootstrap-clean-blog/1.0.3/css/clean-blog.min.css">


footer.jspf

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<footer>
    <div class="container">
        <div class="row">
            ...
        </div>
    </div>
</footer>
<script src="/webjars/jquery/2.1.3/dist/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.5/dist/js/bootstrap.min.js"></script>



nav.jspf

	<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
	<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
	<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
	...
	</nav>


글로벌 네비게이션 바도 많이 써서. 뽑아다.


그리고 다른 jsp들에는

<%@ include file="/WEB-INF/jspf/head.jspf" %>
...
<%@ include file="/WEB-INF/jspf/nav.jspf" %>
...
<%@ include file="/WEB-INF/jspf/footer.jspf" %>


이런식으로 적어주면 된다.

하는김에 대문도 좀 변경하고. post.jsp 파일 타이틀은

<title><c:out value="${post.title}" escapeXml="true"></c:out> : Spring Blog</title>

이렇게 변경하였다.


form.jsp는 .. 에디터로 인해.. 기본형태와 달라서. 그냥 수정하였다.




결과적으로 이런 깔끔한 모습이 되었다.


기타 파일들 좀 정리도 하였다.

application.yml 파일의 logging level 도 수정하였는데.. 이부분은 나중에 설명할 시간이 있을 것이다.




다음 글은 아마도.. 7월 첫주가 지나고 작성할 수 있을 것 같아서 주말동안 글을 두개나 적어 보았다.

가능하면 한주에 하나 이상 글을 쓰려고 하고 있었지만. 회사일로 좀 바빠서 그러니 이해 부탁드린다. 


뭔가 장황하고 쓸모없는 말을 많이 한것 같은 오늘인데.. 아마 코드를 보면 더 쉬울 것이다.

오늘 작업한 코드는 https://github.com/origoni/Spring-Blog/tree/v0.0.13 여기를 참고 부탁드린다.


라이브 데모 : http://blog.millky.com/post/list

변경사항은 : https://github.com/origoni/Spring-Blog/commit/207dea1172643e4763a82280bf75a47696c0167d

깃헙 : https://github.com/origoni/Spring-Blog

프로젝트 다운받아 시작하는 방법은(for Windows User)? : http://millky.com/@origoni/post/1145

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

역순 정렬도 있어요.. : http://millky.com/@origoni/folder/30/post/list?sort=regDate,asc



sisi 2015-06-28 09:00:50

데모 페이지에 글을 쓸 수 없습니다.

sisi 2015-06-28 09:02:28

앱 설정 안 됨: This app is still in development mode, and you don't have access to it. Switch to a registered test user or ask an app admin for permissions.

origoni 2015-06-28 10:34:11

안녕하세요 ㅠㅠ 제보 감사드립니다 ㅠㅠ
지금 수정하였습니다.
App status 설정을 잘못 하였습니다 (기본이 비공개라ㅠㅠ)
감사합니다.

DExWtSbs 2023-06-12 15:58:08

555

DExWtSbs 2023-06-12 15:58:18

555

DExWtSbs 2023-06-12 15:58:30

555

Jshsakura 2015-09-02 20:38:51

정말 이블로그에서 많은자료를 얻고있습니다. 덕분에 스프링부트 인덱스를 띄워보았네요. 덕분에 암이 나았습니다. 감사합니다.

DExWtSbs 2023-06-12 15:58:14

555

DExWtSbs 2023-06-12 15:58:24

555

DExWtSbs 2023-06-12 15:58:36

555
back to top