반응형
https://github.com/jaewon2336/jpa-paging-search
1. 글 목록을 뿌리기 위한 간단한 화면 구현
mustache 템플릿 엔진 사용
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<style>
.board {
border: 1px solid black;
height: 800px;
margin: 50px;
border-radius: 10px;
}
.container mt-3 {
margin: 50px;
}
.pagination {
margin: 350px;
}
.btn_box {
display: flex;
justify-content: left;
padding-left: 50px;
}
.page_box {
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<div class="container-fluid">
<ul class="navbar-nav">
<a class="nav-link active" href="#">Everytime</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">logout</a>
</li>
<li class="nav-item">
<form class="d-flex justify-content-end">
<input id="keyword" name="keyword" class="me-2" type="text" placeholder="Search">
<button id="btn-search" class="btn btn-primary" type="button">검색</button>
</form>
</li>
</ul>
</div>
</nav>
<div class="row content text-center">
<div class="col-sm-8 text-left right container">
<div class="board">
<table class="table">
<thead>
<tr>
<th class="col-1">번호</th>
<th class="col-3">제목</th>
<th class="col-4">내용</th>
<th class="col-3">작성일</th>
</tr>
</thead>
<tbody id="post-box">
</tbody>
</table>
<div class="btn_box">
<button type="button" class="btn btn-dark">새글쓰기</button>
</div>
<div class="page_box">
<ul class="pagination">
<li id="li-prev" class="page-item">
<a id="btn-prev" class="page-link" href="javascript:;">이전</a>
</li>
<li id="li-next" class="page-item">
<a id="btn-next" class="page-link" href="javascript:;">다음</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>
2. 엔티티 생성
테스트를 위한 엔티티이기 때문에 자세한 설정은 하지 않았다.
package site.metacoding.pagingsearch.domain;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 300, nullable = false)
private String title;
@Column(nullable = false)
private String content;
@CreatedDate
private LocalDateTime createDate;
}
3. DB에 더미 데이터 넣기
INSERT INTO post(title,content,createDate) VALUES('스프링부트1강', '프로젝트 세팅하기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트2강', 'yml 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트3강', '뷰 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트11강', '프로젝트 세팅하기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트12강', 'yml 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트13강', '뷰 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트21강', '프로젝트 세팅하기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트32강', 'yml 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트43강', '뷰 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트51강', '프로젝트 세팅하기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트52강', 'yml 만들기', NOW());
INSERT INTO post(title,content,createDate) VALUES('스프링부트53강', '뷰 만들기', NOW());
4. 자바스크립트로 fetch 요청
키워드와 페이지 정보를 담아 요청한다.
컨트롤러로부터 응답을 받으면 그림을 그려줄 것이다.
let page = 0;
let keyword = $("#keyword").val(""); // 초기화
// 이전버튼 이벤트
$("#btn-prev").click(() => {
page--;
$("#post-box").empty();
let keyword = $("#keyword").val(); // 키워드 가지고 가야함
list(keyword);
});
// 다음버튼 이벤트
$("#btn-next").click(() => {
page++;
$("#post-box").empty();
let keyword = $("#keyword").val();
list(keyword);
});
// 검색버튼 이벤트
$("#btn-search").click(() => {
page = 0; // 페이지 초기화 검색한 페이지가 0번!
keyword = $("#keyword").val();
$("#post-box").empty();
list(keyword);
});
async function list(keyword) {
let response = await fetch(`/api/list?page=${page}&keyword=${keyword}`);
let responseParse = await response.json();
if (response.status === 200) {
pagingDisabled(responseParse);
responseParse.content.forEach((post) => {
$("#post-box").append(postItemRender(post));
});
} else {
alert("잘못된 요청입니다.");
}
}
function postItemRender(post) { // 그림 그리기 CSR
return `<tr>
<td>${post.id}</td>
<td>${post.title}</td>
<td>${post.content}</td>
<td>${post.createDate}</td>
</tr>`;
}
function pagingDisabled(responseParse) {
if (responseParse.first == true) {
$("#li-prev").addClass("disabled");
$("#li-next").removeClass("disabled");
} else if (responseParse.last == true) {
$("#li-prev").removeClass("disabled");
$("#li-next").addClass("disabled");
} else {
$("#li-prev").removeClass("disabled");
$("#li-next").removeClass("disabled");
}
}
list(""); // 메인 페이지에서 바로 실행되게!
5. 컨트롤러 연결
JPA가 제공하는 페이징 기능을 사용하기 위해
파라미터로 Pageable 타입을 넘겨준다.
Pageable은 인터페이스인데
실제로는 PageRequest 객체를 생성해준다.
@PageableDefault 어노테이션으로 Pageable의 기본 속성을 바꿔줄 수 있다.
Pageable의 기본 속성
page: 현재 페이지, 시작 넘버가 0인것에 주의하자.
size: 한 페이지에 노출할 데이터 건수
sort: 정렬 조건을 정의한다.
예) 정렬 속성,정렬 속성...(ASC | DESC),
정렬 방향을 변경하고 싶으면 sort 파라미터 추가 (asc 생략 가능)
@GetMapping("/api/list")
public ResponseEntity<?> list(String keyword, Integer page,
@PageableDefault(size = 5, sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
Page<Post> posts = postService.글목록보기(keyword, pageable);
return new ResponseEntity<>(posts, HttpStatus.OK);
}
6. 서비스 연결
public Page<Post> 글목록보기(String keyword, Pageable pageable) {
return postRepository.findByTitleContaining(keyword, pageable);
}
7. 레파지토리 연결 -> 쿼리 생성
Pageable을 사용하면 Page<?> 타입을 리턴해준다.
@Query(value = "SELECT * FROM post WHERE title like %:keyword%", nativeQuery = true)
Page<Post> findByTitleContaining(@Param("keyword") String keyword, Pageable pageable);
결과 화면
▼
메인 페이지 5개씩 페이징 완료
첫번째 페이지에서 이전 버튼 비활성화
이전버튼, 다음버튼 활성화 정상
마지막 페이지에서 다음 버튼 비활성화
키워드 = "1강" 검색
반응형