https://www.youtube.com/watch?v=iIyx2rS8FQw&list=PLV9zd3otBRt7jmXvwCkmvJ8dH5tR_20c0&index=10
참고 영상입니다.
https://github.com/chogigang/spring-file-comment
해당 깃 허브에서 DTO,Entity 정보를 참고하시길 바랍니다.
페이징 처리
파라미터와 어떤 역활을 하는지
알아봅시다.
주소창에 값이 있다는건 서버로 요청을 했다는 걸로 이해하면 됩니다.
이제 컨트롤러에서 어떤 파라미터값들을 받아주는 코드가 필요하고
그 파라미터 값들을 이용해서 우리의 데이터베이스로부터 그 해당 필요한 자료들을
가져오게 되는 것이 페이징 처리입니다.
가장 기본적인 페이징 처리는 사용자가 어떤 특정 페이지 번호를 클릭했을 때
그 페이지에 해당하는 데이터만 화면에 보여주면 됩니다.
이런 개념이 페이징 처리에 개념입니다.
페이징 처리의 주소
게시판 기준 (board.paging)
/board/paging?page=2 //쿼리 스트링으로 파라미터를 넘기는 방법
/board/paging/2 <<게시글에 갯수가 바뀔 때 목록이 달라질 수 있기 때문에 비추천
일단 html 쪽부터 작성을 시작합시다.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<button onclick="saveReq()">글작성</button>
<a href="/board/save">글작성(링크)</a>
<button onclick="listReq()">글목록</button>
<button onclick="pagingReq()">페이징목록</button><!--페이징 추가 -->
</body>
<script>
// function saveReq() {
//
// }
const saveReq = () => {
location.href = "/board/save";
}
const listReq = () => {
location.href = "/board/";
}
const pagingReq = () => { <추가 (복붙시 추가 설명 문구 삭제 해야함)
location.href = "/board/paging";
}
</script>
</html>
함수를 받기 위해서 pagingReq = {() location.href = "/board/paging";} 로 주소를 요청합니다
기본적으로 페이징 목록을 요청하면 1페이지가 보이게끔 처리를 할 것입니다.
그리고 화면도 따로 만들어서 잡을 생각입니다.
이제 주소 요청을 했으니 컨트롤러를 만들어야 합니다.
리퀘스트 팜 객체를 이용해도 되지만 하지만 스프링에서 제공하는 페이지 관련된 방식이 있습니다. 활용도가 높진 않습니다.
@PageableDefault
사이즈는 얼마, 정렬은 어떻게 등을 기본으로 잡아주기 위해 @PageableDefault를 사용합니다.
예를 들어서 http://localhost:8080/persons?page=1&size=20 이런식으로 파리미터 page,size,sort 를 쓰시면 됩니다.
그리고 중요한 것이 예를 들어 게시글이 14라고 하고
한 페이지에 5개씩 글을 본다고 하면 필요한 페이지의 개수는3개가 필요할 것입니다. 한페이지에 5개씩 => 3개
한페이지에 3개씩 본다고 한다면 5개가 필요합니다. 한페이지에 3개씩 =>5개
예를 들어서 만약에 네이버 쇼핑에서 80개씩 본다고 한다 하면
pagingIndex 로 호출한 페이지는 몇인지 pagingSize 가 몇인지 이런 것들을 다 서버 쪽으로 요청을 같이 하고 있습니다.
그래야 하단에서 보여주게 되는 페이지 번호도 바뀌기 때문입니다.
하지만 지금은 단순하게 몇 페이지의 요청을 받았냐 이것만 좀 추가하려고 합니다.
BoardController
//게시글 페이징
// /board/paging?page=1
@GetMapping("/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {//꼭 스프링 옵션이 있는 Pageable 선택해야한다 자바 Pageable 선택하면 안된다.
//pageable.getPageNumber();
Page<BoardDTO> boardList = boardService.paging(pageable);
- pageable이라는 객체는 파라미터에서 page라고 지정된 파라미터 값을 받아줍니다. pageNumber라는 값으로 몇 페이지가 요청됐는지 값을 사용할 수 있습니다.
- @PageableDefault(page = 1) 이렇게 정한 이유는 시작 인덱스 값 주소도 마찬가지로 페이지 번호를 따로 언급을 안 했습니다. 그런 경우는 기본적으로 1페이지를 사용자한태 준다는 뜻입니다. /board/paging?page=1 값이 없더라도 그냥 기본으로 지정된 page = 1 지정된 페이지 값을 지정해서 사용하겠다는 옵션입니다. 만약 이런 게 없으면 페이지 값이 없어서 에러가 날 수도 있기 때문에 지정하는 것이 좋습니다.
- 페이징 처리된 데이터를 가지고 화면으로 넘어가야 하기 때문에 Model model 사용했습니다.
- Page<BoardDTO> ; Page객체를 임포트 하면 스프링에서 제공하는 인터페이스를 사용할 수 있습니다
- 게시글을 페이징 처리해서 BoardDto 담긴 페이지 객체를 가져옵니다.
- 서비스에서 paging이라는 메소드를 호출해서 결과값을 가져오겠다라는 것입니다. 메소드는 paging 결과값은 pageable은 페이지 값을 가져오기 위해서 pageable 로 지정합니다
서비스 클래스에서 paging 메서드를 정의를 해야 합니다. 하지만 그전에 목록: id,writer,title,hits,createdTime의 정보를 담고 있는 DTO생성자를 추가해야 합니다.
BoardDTO
package com.cho.board.dto;
import com.cho.board.entity.BaseEntity;
import com.cho.board.entity.BoardEntity;
import com.cho.board.entity.BoardFileEntity;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
//DTO(Data Transfer Object)
@Getter
@Setter
@ToString
@NoArgsConstructor // 기본생성자
@AllArgsConstructor // 모든 필드를 매개변수로 하는 생성자
public class BoardDTO {
// CRUD 파트
private Long id; //게시글 id
private String boardWriter; //글쓴이
private String boardPass; // 비번
private String boardTitle; // 게시글 이름
private String boardContents; //게시글 내용
private int boardHits; //조회수
private LocalDateTime boardCreatedTime; //게시글 작성 시간
private LocalDateTime boardUpdatedTime; // 게시글 수정시간
//개시글 파일 첨부 용 파일이 한 게시물에 다중으로 있을때 앞에 List<>을 추가해야한다.
private List<MultipartFile> boardFile;//첨부파일 스프링 인터페이스다. save.html - > Controller 파일을 담는 용도 , 보드 컨트롤러 만 적용
private List<String> originalFileName;//원본 파일 이름 ,나머지는 보드 서비스에 적용
private List<String> storedFileName;// 서버 저장용 파일 이름
private int fileAttached;//파일 첨부 여부(첨부 1 , 미첨부 0) 이걸 설정안하면 엔티티에서 귀찮게 설정을 많이 해야하는대 이렇게 하니 개인적으로 편해서 씀
//개시글 페이징용
public BoardDTO(Long id, String boardWriter, String boardTitle, int boardHits, LocalDateTime boardCreatedTime) {
this.id = id;
this.boardWriter = boardWriter;
this.boardTitle = boardTitle;
this.boardHits = boardHits;
this.boardCreatedTime = boardCreatedTime;
}
//위에있는거 DTO 를 엔티티로 넘기는 작업이다 복잡한거 없다.
public static BoardDTO toBoardDTO(BoardEntity boardEntity) {
BoardDTO boardDTO = new BoardDTO();
boardDTO.setId(boardEntity.getId());
boardDTO.setBoardWriter(boardEntity.getBoardWriter());
boardDTO.setBoardPass(boardEntity.getBoardPass());
boardDTO.setBoardTitle(boardEntity.getBoardTitle());
boardDTO.setBoardContents(boardEntity.getBoardContents());
boardDTO.setBoardHits(boardEntity.getBoardHits());
boardDTO.setBoardCreatedTime(boardEntity.getCreatedTime());
boardDTO.setBoardUpdatedTime(boardEntity.getUpdatedTime());
//파일이 없다면
if(boardEntity.getFileAttached()== 0){
boardDTO.setFileAttached(boardEntity.getFileAttached()); //0
//파일이 있는경우
}else{
boardDTO.setFileAttached(boardEntity.getFileAttached()); // 1
List<String> originalFileNameList = new ArrayList<>();
List<String> storedFileNameList = new ArrayList<>();
for (BoardFileEntity boardFileEntity: boardEntity.getBoardFileEntityList()) {
originalFileNameList.add(boardFileEntity.getOriginalFileName());
storedFileNameList.add(boardFileEntity.getStoredFileName());
// BoardDTO의 originalFileName이 List이기 때문에 add()를 이용하여
// boardFileEntity에 있는 originalFileName을 옮겨 담음.
}
boardDTO.setOriginalFileName(originalFileNameList);
boardDTO.setStoredFileName(storedFileNameList);
}
return boardDTO;
}
// 첨부파일이 여러개면 반복문을 써야한다 이건 첨부파일이 단일일때만 쓰는 코드다
// boardDTO.setOriginalFileName(boardEntity.getBoardFileEntityList().get(0).getOriginalFileName());
// boardDTO.setStoredFileName(boardEntity.getBoardFileEntityList().get(0).getStoredFileName());
}
엔티티에서 커맨드 n키를 누르면 자동 추가하는 기능이 있습니다 아니면 우클릭하고 Generate(생성)
하면 자동 완성 기능이 있습니다. 그중 맨 위 생성자 선택하면 편하게 작업할 수 있습니다. getter,setter도 가능합니다.
그중 필요한 것을 골라 생성을 누르면 해당 코드가 자동으로 생성해 줍니다.
public BoardDTO(Long id, String boardWriter, String boardTitle, int boardHits, LocalDateTime boardCreatedTime) {
this.id = id;
this.boardWriter = boardWriter;
this.boardTitle = boardTitle;
this.boardHits = boardHits;
this.boardCreatedTime = boardCreatedTime;
}
이렇게 자동 완성 해줍니다.
BoardService
//페이징 처리
public Page<BoardDTO> paging(Pageable pageable) {
int page = pageable.getPageNumber() -1;
int pageLinit = 3; // 한페이지 보여줄 글 갯수
//한페이지당 3개글씩 보여주고 정렬기준은 id 기준으로 내림차순 정렬
Page<BoardEntity> boardEntities= // 몇페이지, 한페이지 여줄 글 갯수,어떻게 정렬해서 가져올거냐 전체를 여기 기준으로 해당페이지값을 가저온다
boardRepository.findAll(PageRequest.of(page, pageLinit, Sort.by(Sort.Direction.DESC,"id")));// Sort.Direction 내림차순 id 는 엔티티 의 작성한 이름 기준
System.out.println("boardEntities.getContent() = " + boardEntities.getContent()); // 요청 페이지에 해당하는 글
System.out.println("boardEntities.getTotalElements() = " + boardEntities.getTotalElements()); // 전체 글갯수
System.out.println("boardEntities.getNumber() = " + boardEntities.getNumber()); // DB로 요청한 페이지 번호
System.out.println("boardEntities.getTotalPages() = " + boardEntities.getTotalPages()); // 전체 페이지 갯수
System.out.println("boardEntities.getSize() = " + boardEntities.getSize()); // 한 페이지에 보여지는 글 갯수
System.out.println("boardEntities.hasPrevious() = " + boardEntities.hasPrevious()); // 이전 페이지 존재 여부
System.out.println("boardEntities.isFirst() = " + boardEntities.isFirst()); // 첫 페이지 여부
System.out.println("boardEntities.isLast() = " + boardEntities.isLast()); // 마지막 페이지 여부
//엔티티 객체를 옮겨서 DTO 로 변환하면서 위 객체들을 옮겨주는 것
//목록 id, writer, title, hist,createdTime
Page<BoardDTO> boardDTOS = boardEntities.map(board -> new BoardDTO(board.getId(), board.getBoardWriter(), board.getBoardTitle(), board.getBoardHits(),board.getCreatedTime()));
return boardDTOS;
}
- 한 페이지당 3개씩 글을 보여주고 정렬 기준은 id 기준으로 내림차순 정렬을 레포지토리로부터 가져오는 거기 때문에 <BoardEntity>가 담겨있고 스프링 JPA에서 리턴을 페이지 객체로 리턴을 줄 수 있습니다.
- pageable.getpageNumber()-1; 는 어떤 페이지를 요청했느냐 메소드 호출을 이용해서 가져올겁니다. 굳이 -1 하는 이유는 보고자 하는 페이지 그 자리에 위치하는 값이 0부터 시작하기 때문이기에 실제 사용자가 요청한 페이지 값에서 하나 빠진 값이 요청이 돼야 합니다. 사용자한태 혼선을 주지 않기 위해서 하는 것입니다.
- 밑 프린트들은 필요한 정보입니다 주로 쓰는 용도로 정보를 작성했습니다.
- boardEntities 는 엔티티로 담겨 있는 객체이다 보니 Dto로 가져가야 합니다. Page<BoardDTO> boardDTO =
변환을 해줍니다.
다시 Controller (컨트롤러)로 돌아와서 boardList로 리턴을 받습니다. 그리고 보여지는 페이지 겟수, 시작 페이지, 끝 페이지를 구현해야 합니다
BoardController
//게시글 페이징
// /board/paging?page=1
@GetMapping("/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {//꼭 스프링 옵션이 있는 Pageable 선택해야한다 자바 Pageable 선택하면 안된다.
//pageable.getPageNumber();
Page<BoardDTO> boardList = boardService.paging(pageable);
int blockLimit = 3;
int startPage = (((int)(Math.ceil((double)pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1; // 1 4 7 10 ~~
int endPage = ((startPage + blockLimit - 1) < boardList.getTotalPages()) ? startPage + blockLimit - 1 : boardList.getTotalPages();
// page 갯수 20개
// 현재 사용자가 3페이지
// 1 2 3
// 현재 사용자가 7페이지
// 7 8 9
// 보여지는 페이지 갯수 는 3
// 총 페이지 갯수 8개
model.addAttribute("boardList", boardList);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "paging";
- int startPage = 현재 사용자가1또는 2또는3페이지에 있으면 스타트 페이지는 1 이라는 값을 갖도록 하는 것
혹여나 7,8,9페이지에 있으면 시작페이지는 7이라는 값을 만들어 주는 계산을 하는 것입니다 현재 사용자가 요청한 페이지를 /blockLimit 으로 나눠서 소숫점을 올리는 처리를 하는것이고 여기서 1을 뺀다음 그페이지 값을 곱해서 +1을 합니다 - int endPage 실제 전체 페이지 개수가 작은 값을 갖고 있다면 9라는 값을 보여주지말고 전체 페이지 값을 엔드 페이지 값으로 하는 코드 입니다 3항 연산자를 사용 했습니다.
- 모델 값으로 List,startPage,endPage 를 구현하고 리턴으로 paging html로 넘어가도록 리턴을 해줍니다.
개시글 리스트. html 도 상관없습니다 컨트롤러나 주소 링크를 잘 수정하면 어디로든 페이징 주소를 줄 수 있으니 잘 수정하시기 바랍니다.
paging.Html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="saveReq()">글작성</button>
<table>
<tr>
<th>id</th>
<th>title</th>
<th>writer</th>
<th>date</th>
<th>hits</th>
</tr>
<tr th:each="board: ${boardList}">
<td th:text="${board.id}"></td>
<td><a th:href="@{|/board/${board.id}|(page=${boardList.number + 1})}" th:text="${board.boardTitle}"></a></td>
<td th:text="${board.boardWriter}"></td>
<td th:text="*{#temporals.format(board.boardCreatedTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${board.boardHits}"></td>
</tr>
</table>
<!-- 첫번째 페이지로 이동 -->
<!-- /board/paging?page=1 -->
<a th:href="@{/board/paging(page=1)}">First</a>
<!-- 이전 링크 활성화 비활성화 -->
<!-- boardList.getNumber() 사용자:2페이지 getNumber()=1 -->
<a th:href="${boardList.first} ? '#' : @{/board/paging(page=${boardList.number})}">prev</a>
<!-- 페이지 번호 링크(현재 페이지는 숫자만)
for(int page=startPage; page<=endPage; page++)-->
<span th:each="page: ${#numbers.sequence(startPage, endPage)}">
<!-- 현재페이지는 링크 없이 숫자만 -->
<span th:if="${page == boardList.number + 1}" th:text="${page}"></span>
<!-- 현재페이지 번호가 아닌 다른 페이지번호에는 링크를 보여줌 -->
<span th:unless="${page == boardList.number + 1}">
<a th:href="@{/board/paging(page=${page})}" th:text="${page}"></a>
</span>
</span>
<!-- 다음 링크 활성화 비활성화
사용자: 2페이지, getNumber: 1, 3페이지-->
<a th:href="${boardList.last} ? '#' : @{/board/paging(page=${boardList.number + 2})}">next</a>
<!-- 마지막 페이지로 이동 -->
<a th:href="@{/board/paging(page=${boardList.totalPages})}">Last</a>
</body>
<script>
const saveReq = () => {
location.href = "/board/save";
}
</script>
</html>
BoardController
//해당 게시글의 조회수를 하나 올리고 게시글데이터를 가져와서 detail.html에 출력
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model,
@PageableDefault(page = 1) Pageable pageable) {//추가
boardService.updateHits(id);
BoardDTO boardDTO =boardService.findById(id);
/* 댓글 목록 가져오기*/
List<CommentDTO> commentDTOList = commentService.findAll(id);
model.addAttribute("commentList",commentDTOList);
model.addAttribute("board",boardDTO);
model.addAttribute("page",pageable.getPageNumber());//추가
return "detail";
}
2페이지에서 넘어왔다고 하는 걸 보여주는 것입니다.
진행했던 순서도
index.html 작성->controller주소 받아오기 ->컨트롤러에서 페이징이 필요한 서비스 메서드 구현 ->DTO t생성자 구현 ->boardEntities 엔티티에서 DTO로 변환->컨트롤에서 boardList로 리턴을 받아오고 객체 작성 후 리턴값을 paging html로 리턴값을 주기->html에서 페이징 관련 코드 작성->
코드를 제외한 이론만 있는 설명은 해당 영상을 참고 하시면 좋습니다.
https://www.youtube.com/watch?v=uy_tGFmhM_U&list=LL&index=1
기존 list.html 을 사용안하고 paging 을 메인 list 화면으로 사용하겠습니다.
// 게시글 목록
// @GetMapping("/")
// public String findAll(Model model) {
// // DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
// List<BoardDTO> boardDTOList = boardService.findAll();
//
// model.addAttribute("boardList", boardDTOList);
// return "list";
// }
//게시
//게시글 페이징
// /board/paging?page=1
@GetMapping("/paging")
public String paging(@PageableDefault(page = 1) Pageable pageable, Model model) {
//pageable.getPageNumber();
Page<BoardDTO> boardList = boardService.paging(pageable);
List<BoardDTO> boardDTOList = boardService.findAll(); //추가
int blockLimit = 3;
int startPage = (((int)(Math.ceil((double)pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1; // 1 4 7 10 ~~
int endPage = ((startPage + blockLimit - 1) < boardList.getTotalPages()) ? startPage + blockLimit - 1 : boardList.getTotalPages();
// page 갯수 20개
// 현재 사용자가 3페이지
// 1 2 3
// 현재 사용자가 7페이지
// 7 8 9
// 보여지는 페이지 갯수 는 3
// 총 페이지 갯수 8개
model.addAttribute("boardList", boardDTOList); //추가
model.addAttribute("boardList", boardList);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "paging";
}
}
기존에 리스트 에있었던 model.addAttribute("boardList",boardDTOList) ,List<BoardDTO> boardDTOList =boardService.finAll() 를 추가하면 그냥 끝이긴합니다.
list.html도 주석 처리 했습니다.
index 에서도 게시글 목록 가는방향도 다 paging으로 바꿔봤습니다.
// function saveReq() {
//
// }
const saveReq = () => {
location.href = "/board/save";
}
const listReq = () => {
location.href = "/board/paging";
}
const pagingReq = () => {
location.href = "/board/paging";
}
잘 돌아갑니다. 기존 메인 리스트 페이지에서 페이징을 추가 하는게 일반적이지만 저는 하다보니 tymelef 오류를 못고처서 그냥 이렇게 했습니다 ....
'Spring > 게시판 CRUD + 추가기능들' 카테고리의 다른 글
로그인 구현 (0) | 2023.04.03 |
---|---|
회원가입 기능 구현 (0) | 2023.04.01 |
게시판 삭제 (0) | 2023.03.29 |
게시글 수정 (0) | 2023.03.29 |
게시글 상세조회 (0) | 2023.03.28 |