JPA를 이용하여 특정 페이지 처리하기
게시판 밑에 버튼 모양 양식 가져오기
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