Loading...

프로젝트/에브리타임 / / 2022. 4. 14. 14:47

[프로젝트] JPA 페이징 + 검색기능 구현 (fetch)

반응형

https://github.com/jaewon2336/jpa-paging-search

 

GitHub - jaewon2336/jpa-paging-search

Contribute to jaewon2336/jpa-paging-search development by creating an account on GitHub.

github.com

 

 

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강" 검색

 

반응형