프로젝트/Spring Boot

JPA를 이용하여 특정 페이지 처리하기

라임07 2022. 10. 27. 00:27

게시판 밑에 버튼 모양 양식 가져오기

https://getbootstrap.com/docs/4.5/components/pagination/#overview

 

Pagination

Documentation and examples for showing pagination to indicate a series of related content exists across multiple pages.

getbootstrap.com

 

Bootstrap 페이지처리 코드를 복사합니다.

 

 

 

 

  • list.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/common :: head('게시판')">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <link href="starter-template.css" th:href="@{/starter-template.css}" rel="stylesheet">

    <title>게시판</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('board')">
    <!--        <a class="navbar-brand" href="#">SUNShower</a>-->
    <!--        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">-->
    <!--            <span class="navbar-toggler-icon"></span>-->
    <!--        </button>-->

    <!--        <div class="collapse navbar-collapse" id="navbarsExampleDefault">-->
    <!--            <ul class="navbar-nav mr-auto">-->
    <!--                <li class="nav-item active">-->
    <!--                    <a class="nav-link" href="#">홈 <span class="sr-only">(current)</span></a>-->
    <!--                </li>-->
    <!--                <li class="nav-item">-->
    <!--                    <a class="nav-link" href="#">게시판</a>-->
    <!--                </li>-->
    <!--            </ul>-->
    <!--        </div>-->
</nav>

<div class="container">
    <h2>게시판</h2>
    <div>총 건수 : <span th:text="${#lists.size(boards)}"></span></div>
    <table class="table">
        <thead class="thead-dark">
        <tr>
            <th scope="col">번호</th>
            <th scope="col">제목</th>
            <th scope="col">작성자</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="board : ${boards}">
            <td th:text="${board.id}">Mark</td>
            <td><a th:text="${board.title}" th:href="@{/board/form(id=${board.id})}">Otto</a></td>
            <td>홍길동</td>
        </tr>
    </table>
    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center">
            <li class="page-item disabled">
                <a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
            </li>
            <li class="page-item"><a class="page-link" href="#">1</a></li>
            <li class="page-item"><a class="page-link" href="#">2</a></li>
            <li class="page-item"><a class="page-link" href="#">3</a></li>
            <li class="page-item">
                <a class="page-link" href="#">Next</a>
            </li>
        </ul>
    </nav>
    <div class="text-right">
        <a type="button" class="btn btn-primary" th:href="@{/board/form}">쓰기</a>
    </div>
</div>

</main>

<!-- Optional JavaScript -->
<!-- jQuery, Popper.js, and Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
</body>
</html>

list.html 안에 table, "쓰기" 버튼 사이에 Bootstrap에서 가져온 양식을 붙여넣습니다.

 

 

 

 

정상적으로 실행되고 있음을 확인할 수 있습니다.

 

 

 

 

총 건수 List 형식을 Page 형식으로 변경하기

  • BoardController

package com.example.layout.controller;

import com.example.layout.model.Board;
import com.example.layout.repository.BoardRepository;
import com.example.layout.validator.BoardValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@Controller
@RequestMapping("/board")
public class BoardController {

    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private BoardValidator boardValidator;

    @GetMapping("/list")
    public String list(Model model){
        Page<Board> boards = boardRepository.findAll(PageRequest.of(0, 20));
        model.addAttribute("boards", boards);
        return "board/list";
    }

    @GetMapping("/form")
    public String form(Model model, @RequestParam(required = false) Long id){
        if(id == null){
            model.addAttribute("board", new Board());
        } else {
            Board board = boardRepository.findById(id).orElse(null);
            model.addAttribute("board", board);
        }

        return "board/form";
    }

    @PostMapping("/form")
    public String greetingSubmit(@Valid Board board, BindingResult bindingResult) {
        boardValidator.validate(board, bindingResult);
        if(bindingResult.hasErrors()){
            return "board/form";
        }
        boardRepository.save(board);
        return "redirect:/board/list";
    }
}

List 형태의 Board를 Page 형태로 변경합니다.(JPA는 Page 시작이 0이므로 1이 아닌 0으로 수정합니다.)

 

 

 

 

Page로 바꾸었기에 총 건수는 1건으로 되어있습니다.

 

 

 

 

  • list.html

 

list.size로 되어있던 부분을 boards.totalElements로 변경합니다. 

 

 

 

 

총 건수가 정상적으로 작동되고 있음을 확인할 수 있습니다.

 

 

 

 

Pageable을 파라미터 형식으로 바꾸기

 

 

  • ?page=0(1번째 페이지) 조회하기

 

 

  • ?page=1(2번째 페이지) 조회하기

 

 

  • ?page=0(1번째 페이지) & size=2(게시물 2개) 조회하기

 

정상적으로 실행되고 있음을 확인할 수 있습니다.

 

 

 

가져온 양식에서 Page 수를 일정 범위로 수정하기

  • BoardController

@GetMapping("/list")
public String list(Model model, Pageable pageable){
    Page<Board> boards = boardRepository.findAll(pageable);
    int startPage = Math.max(0, boards.getPageable().getPageNumber() - 4);
    int endPage = Math.min(boards.getTotalPages(), boards.getPageable().getPageNumber() + 4);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("boards", boards);
    return "board/list";
}

위와같은 수식으로 페이지 수의 범위를 정합니다.

 

 

 

  • list.html

<nav aria-label="Page navigation example">
    <ul class="pagination justify-content-center">
        <li class="page-item disabled">
            <a class="page-link" href="#" tabindex="-1" aria-disabled="true">◀</a>
        </li>
        <li class="page-item" th:each="i : ${#numbers.sequence(startPage, endPage)}"><a class="page-link" href="#">1</a></li>
        <li class="page-item">
            <a class="page-link" href="#">▶</a>
        </li>
    </ul>
</nav>

위와같이 시작페이지~끝페이지 까지 범위를 지정합니다.

 

 

 

 

111 로 나오기에 다음과 같이 수정합니다.

 

<nav aria-label="Page navigation example">
    <ul class="pagination justify-content-center">
        <li class="page-item disabled">
            <a class="page-link" href="#" tabindex="-1" aria-disabled="true">◀</a>
        </li>
        <li class="page-item" th:each="i : ${#numbers.sequence(startPage, endPage)}">
            <a class="page-link" href="#" th:text="${i}">1</a>
        </li>
        <li class="page-item">
            <a class="page-link" href="#">▶</a>
        </li>
    </ul>
</nav>

 

0,1,2 로 나오기에 다음과같이 수정합니다.

 

 

  • BoardController
@GetMapping("/list")
public String list(Model model, Pageable pageable){
    Page<Board> boards = boardRepository.findAll(pageable);
    int startPage = Math.max(1, boards.getPageable().getPageNumber() - 4);
    int endPage = Math.min(boards.getTotalPages(), boards.getPageable().getPageNumber() + 4);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("boards", boards);
    return "board/list";
}

Math.max 옆에 0으로 되어있던 부분을 1로 수정합니다.

 

 

 

 

 

정상적으로 실행되고 있음을 알 수 있습니다.

 

 

페이지당 나타나는 게시글 수 정하기

  • BoardController
@GetMapping("/list")
public String list(Model model,@PageableDefault(size = 2) Pageable pageable){
    Page<Board> boards = boardRepository.findAll(pageable);
    int startPage = Math.max(1, boards.getPageable().getPageNumber() - 4);
    int endPage = Math.min(boards.getTotalPages(), boards.getPageable().getPageNumber() + 4);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("boards", boards);
    return "board/list";
}

@PageableDefault(size="원하는게시물수") 적으면 원하는 게시글 수를 게시판에 나오게 할 수 있습니다.

(기본은 10개)

 

 

 

 

 

정상적으로 게시글 수가 2개로 나타나고 있음을 확인할 수 있습니다.

 

 

 

 

 

현재 페이지 비활성화하기

  • list.html
<nav aria-label="Page navigation example">
    <ul class="pagination justify-content-center">
        <li class="page-item disabled">
            <a class="page-link" href="#" tabindex="-1" aria-disabled="true">◀</a>
        </li>
        <li class="page-item" th:classappend="${i == boards.pageable.pageNumber + 1} ? 'disabled'" th:each="i : ${#numbers.sequence(startPage, endPage)}">
            <a class="page-link" href="#" th:text="${i}">1</a>
        </li>
        <li class="page-item">
            <a class="page-link" href="#">▶</a>
        </li>
    </ul>
</nav>

th:classappend 문법을 통해 현재 페이지일 경우 링크 비활성화 조건을 지정합니다.

 

 

 

 

1페이지에서 "1"이 비활성화되었음을 확인할 수 있습니다.

 

 

 

2페이지에서 "2"가 비활성화되었음을 확인할 수 있습니다.

 

 

 

 

전페이지, 다음페이지 비활성화하기

  • list.html
<nav aria-label="Page navigation example">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${1 == boards.pageable.pageNumber + 1} ? 'disabled'">
            <a class="page-link" href="#" tabindex="-1" aria-disabled="true">◀</a>
        </li>
        <li class="page-item" th:classappend="${i == boards.pageable.pageNumber + 1} ? 'disabled'" th:each="i : ${#numbers.sequence(startPage, endPage)}">
            <a class="page-link" href="#" th:text="${i}">1</a>
        </li>
        <li class="page-item" th:classappend="${boards.totalPages == boards.pageable.pageNumber + 1} ? 'disabled'">
            <a class="page-link" href="#">▶</a>
        </li>
    </ul>
</nav>

 

 

 

현재 페이지가 1일 때 "이전 버튼"이 비활성화되었음을 확인할 수 있습니다.

 

 

 

 

현재 페이지가 2일 때 "다음 버튼"이 비활성화되었음을 확인할 수 있습니다.

 

 

 

 

링크 활성화 하기

  • list.html
<nav aria-label="Page navigation example">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${1 == boards.pageable.pageNumber + 1} ? 'disabled'">
            <a class="page-link" href="#" th:href="@{/board/list(page=${boards.pageable.pageNumber - 1})}" tabindex="-1" aria-disabled="true">◀</a>
        </li>
        <li class="page-item" th:classappend="${i == boards.pageable.pageNumber + 1} ? 'disabled'" th:each="i : ${#numbers.sequence(startPage, endPage)}">
            <a class="page-link" href="#" th:href="@{/board/list(page=${i - 1})}" th:text="${i}">1</a>
        </li>
        <li class="page-item" th:classappend="${boards.totalPages == boards.pageable.pageNumber + 1} ? 'disabled'">
            <a class="page-link" href="#" th:href="@{/board/list(page=${boards.pageable.pageNumber + 1})}">▶</a>
        </li>
    </ul>
</nav>

th:href 문법을 통해 각각 링크를 활성화 합니다.

 

 

 

 

정상적으로 실행됨을 알 수 있습니다. (클릭시 넘어갑니다.)

 

 

 

 

 

검색기능 추가하기 - 간단히 UI 만들기

https://getbootstrap.com/docs/4.5/components/forms/

 

Forms

Examples and usage guidelines for form control styles, layout options, and custom components for creating a wide variety of forms.

getbootstrap.com

 

Bootstrap에서 양식을 가져옵니다.

 

 

 

 

  •  list.html

총 건수 부분 밑에 붙여넣은 후 필요없는 부분을 수정합니다.

 


※ 오른쪽 끝으로 정렬하기

 

https://getbootstrap.com/docs/4.5/utilities/flex/

 

Flex

Quickly manage the layout, alignment, and sizing of grid columns, navigation, components, and more with a full suite of responsive flexbox utilities. For more complex implementations, custom CSS may be necessary.

getbootstrap.com

 

 

form 형식 안에 복사한 부분을 붙여넣습니다.


 

※ 버튼모양 수정하기

 

https://getbootstrap.com/docs/4.5/components/buttons/#button-tags

 

Buttons

Use Bootstrap’s custom button styles for actions in forms, dialogs, and more with support for multiple sizes, states, and more.

getbootstrap.com

 

 

위에서 보는 것과같이 "btn-light"로 주게되면 색깔이 바뀜을 알 수 있습니다.


<form class="form-inline d-flex justify-content-end" method="GET" th:action="@{/board/list}">
    <div class="form-group mx-sm-3 mb-2">
        <label for="searchText" class="sr-only">Password</label>
        <input type="text" class="form-control" id="searchText" name="searchText">
    </div>
    <button type="submit" class="btn btn-light mb-2">검색</button>
</form>

최종적으로 위와같이 form 양식이 변경됩니다.

 

 

 

 

검색기능 추가하기

  • BoardRepository

Page<Board>를 추가합니다.

 

 

 

  • BoardController

BoardRepository에 만들었던 함수를 사용하고, 기본적으로 searchText는 null 값이므로 @RequestParam을 통해 값을 지정해줍니다.

 

 

 

  • list.html

form 태그이기에 GET 메소드를 통해 모든 파라미터를 넘기기에 검색 기능을 활성화할 수 있습니다.

 

 

 

  • 제목검색

 

 

  • 내용검색

 

정상적으로 실행되고 있음을 알 수 있습니다.

 

 

 

 

 

세세한 부분 수정하기

1) 검색 후 검색창에 내용 담기게 하기

 

 

2) 검색창에 관련 내용, 제목을 검색하면, 이전 혹은 다음버튼 클릭 시 그대로 내용 담기게 하기

 

위와같이 데이터를 추가합니다.

 

 

"안녕" 검색 시 5개의 데이터가 검색됩니다. 이후 2, 3 버튼 클릭시 "안녕" 내용이 그대로 담깁니다.

 

 

 

 

최종코드(수정한 부분만) - BoardController, BoardRepository, list.html

  • BoardController
package com.example.layout.controller;

import com.example.layout.model.Board;
import com.example.layout.repository.BoardRepository;
import com.example.layout.validator.BoardValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@Controller
@RequestMapping("/board")
public class BoardController {

    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private BoardValidator boardValidator;

    @GetMapping("/list")
    public String list(Model model,@PageableDefault(size = 2) Pageable pageable,
                       @RequestParam(required = false, defaultValue = "") String searchText){
    //    Page<Board> boards = boardRepository.findAll(pageable);
        Page<Board> boards = boardRepository.findByTitleContainingOrContentContaining(searchText, searchText, pageable);
        int startPage = Math.max(1, boards.getPageable().getPageNumber() - 4);
        int endPage = Math.min(boards.getTotalPages(), boards.getPageable().getPageNumber() + 4);
        model.addAttribute("startPage", startPage);
        model.addAttribute("endPage", endPage);
        model.addAttribute("boards", boards);
        return "board/list";
    }

    @GetMapping("/form")
    public String form(Model model, @RequestParam(required = false) Long id){
        if(id == null){
            model.addAttribute("board", new Board());
        } else {
            Board board = boardRepository.findById(id).orElse(null);
            model.addAttribute("board", board);
        }

        return "board/form";
    }

    @PostMapping("/form")
    public String greetingSubmit(@Valid Board board, BindingResult bindingResult) {
        boardValidator.validate(board, bindingResult);
        if(bindingResult.hasErrors()){
            return "board/form";
        }
        boardRepository.save(board);
        return "redirect:/board/list";
    }
}

 

 

  • BoardRepository
package com.example.layout.repository;

import com.example.layout.model.Board;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface BoardRepository extends JpaRepository<Board, Long> {
    List<Board> findByTitle(String title);
    List<Board> findByTitleOrContent(String title, String content);
    Page<Board> findByTitleContainingOrContentContaining(String title, String content, Pageable pageable);
}

 

 

  • list.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/common :: head('게시판')">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <link href="starter-template.css" th:href="@{/starter-template.css}" rel="stylesheet">

    <title>게시판</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('board')">
    <!--        <a class="navbar-brand" href="#">SUNShower</a>-->
    <!--        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">-->
    <!--            <span class="navbar-toggler-icon"></span>-->
    <!--        </button>-->

    <!--        <div class="collapse navbar-collapse" id="navbarsExampleDefault">-->
    <!--            <ul class="navbar-nav mr-auto">-->
    <!--                <li class="nav-item active">-->
    <!--                    <a class="nav-link" href="#">홈 <span class="sr-only">(current)</span></a>-->
    <!--                </li>-->
    <!--                <li class="nav-item">-->
    <!--                    <a class="nav-link" href="#">게시판</a>-->
    <!--                </li>-->
    <!--            </ul>-->
    <!--        </div>-->
</nav>

<div class="container">
    <h2>게시판</h2>
    <div>총 건수 : <span th:text="${boards.totalElements}"></span></div>
    <form class="form-inline d-flex justify-content-end" method="GET" th:action="@{/board/list}">
        <div class="form-group mx-sm-3 mb-2">
            <label for="searchText" class="sr-only">Password</label>
            <input type="text" class="form-control" id="searchText" name="searchText" th:value="${param.searchText}">
        </div>
        <button type="submit" class="btn btn-light mb-2">검색</button>
    </form>
    <table class="table">
        <thead class="thead-dark">
        <tr>
            <th scope="col">번호</th>
            <th scope="col">제목</th>
            <th scope="col">작성자</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="board : ${boards}">
            <td th:text="${board.id}">Mark</td>
            <td><a th:text="${board.title}" th:href="@{/board/form(id=${board.id})}">Otto</a></td>
            <td>홍길동</td>
        </tr>
        </tbody>
    </table>
    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${1 == boards.pageable.pageNumber + 1} ? 'disabled'">
                <a class="page-link" href="#" th:href="@{/board/list(page=${boards.pageable.pageNumber - 1}, searchText=${param.searchText})}" tabindex="-1" aria-disabled="true">◀</a>
            </li>
            <li class="page-item" th:classappend="${i == boards.pageable.pageNumber + 1} ? 'disabled'" th:each="i : ${#numbers.sequence(startPage, endPage)}">
                <a class="page-link" href="#" th:href="@{/board/list(page=${i - 1}, searchText=${param.searchText})}" th:text="${i}">1</a>
            </li>
            <li class="page-item" th:classappend="${boards.totalPages == boards.pageable.pageNumber + 1} ? 'disabled'">
                <a class="page-link" href="#" th:href="@{/board/list(page=${boards.pageable.pageNumber + 1}, searchText=${param.searchText})}">▶</a>
            </li>
        </ul>
    </nav>
    <div class="text-right">
        <a type="button" class="btn btn-primary" th:href="@{/board/form}">쓰기</a>
    </div>
</div>

</main>

<!-- Optional JavaScript -->
<!-- jQuery, Popper.js, and Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
</body>
</html>

 

 

 

 

참고자료

https://www.youtube.com/watch?v=hmSPJHtZyp4&list=PLPtc9qD1979DG675XufGs0-gBeb2mrona&index=8 

 

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io