본문 바로가기
Spring/게시판 CRUD + 추가기능들

게시글 페이징

by chogigang 2023. 3. 30.

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);

 

  1.  pageable이라는 객체는 파라미터에서 page라고 지정된 파라미터 값을 받아줍니다. pageNumber라는 값으로 몇 페이지가 요청됐는지 값을 사용할 수 있습니다.
  2. @PageableDefault(page = 1) 이렇게 정한 이유는 시작 인덱스 값 주소도 마찬가지로 페이지 번호를 따로 언급을 안 했습니다. 그런 경우는 기본적으로 1페이지를 사용자한태 준다는 뜻입니다. /board/paging?page=1 값이 없더라도 그냥 기본으로 지정된  page = 1 지정된 페이지 값을  지정해서 사용하겠다는 옵션입니다. 만약 이런 게 없으면 페이지 값이 없어서 에러가 날 수도 있기 때문에 지정하는 것이 좋습니다.
  3. 페이징 처리된 데이터를  가지고 화면으로 넘어가야 하기 때문에 Model model 사용했습니다.
  4. Page<BoardDTO> ;  Page객체를 임포트 하면  스프링에서 제공하는 인터페이스를 사용할 수 있습니다
  5. 게시글을 페이징 처리해서 BoardDto 담긴 페이지 객체를 가져옵니다.
  6. 서비스에서 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;
}
  1. 한 페이지당 3개씩 글을 보여주고 정렬 기준은  id 기준으로 내림차순 정렬을 레포지토리로부터 가져오는 거기 때문에 <BoardEntity>가 담겨있고 스프링 JPA에서 리턴을 페이지 객체로 리턴을 줄 수 있습니다.
  2. pageable.getpageNumber()-1; 는 어떤 페이지를 요청했느냐 메소드 호출을 이용해서 가져올겁니다. 굳이 -1 하는 이유는 보고자 하는 페이지 그 자리에 위치하는 값이 0부터 시작하기 때문이기에 실제 사용자가 요청한 페이지 값에서 하나 빠진 값이  요청이 돼야 합니다. 사용자한태 혼선을 주지 않기 위해서 하는 것입니다.
  3. 밑 프린트들은 필요한 정보입니다 주로 쓰는 용도로 정보를 작성했습니다.
  4. 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";

 

  1. int startPage = 현재 사용자가1또는 2또는3페이지에 있으면 스타트 페이지는 1 이라는 값을 갖도록 하는 것
    혹여나 7,8,9페이지에 있으면 시작페이지는 7이라는 값을 만들어 주는 계산을 하는 것입니다 현재 사용자가 요청한 페이지를  /blockLimit 으로 나눠서 소숫점을 올리는 처리를 하는것이고 여기서 1을 뺀다음 그페이지 값을 곱해서 +1을 합니다
  2. int endPage 실제 전체 페이지 개수가 작은 값을 갖고 있다면  9라는 값을  보여주지말고 전체 페이지 값을 엔드 페이지 값으로 하는 코드 입니다 3항 연산자를 사용 했습니다.
  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