일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- oracle
- AJAX
- JDBC
- JavaScript
- Open Source
- Exception
- jpa
- spring
- PostgreSQL
- Spring Boot
- MSSQL
- Core Java
- error
- maven
- 설정
- IntelliJ
- Docker
- myBatis
- git
- Source
- MySQL
- Python
- 문서
- 오픈소스
- STS
- Tomcat
- Thymeleaf
- ubuntu
- SpringBoot
- Eclipse
- Today
- Total
헤르메스 LIFE
[Spring Boot] 게시판 #3 - 게시판 목록 + 페이징처리 본문
게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다.
게시판 목록의 디자인 및 일부 소스는 도뎡님의 허락을 받아 사용했습니다.
https://congsong.tistory.com/26
스프링 부트(Spring Boot) - 페이징(Paging) & 검색(Search) 처리하기 1/2 [Thymeleaf, MariaDB, IntelliJ, Gradle, MyBat
본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다. DBMS 툴은 DBeaver를 이용하며, DB는 MariaDB를 이용합니다. (MariaDB 설치하기) 화면 처리는 HTML5 기반
congsong.tistory.com
개발환경
STS 4.17.2.RELEASE
OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)
Spring Boot 2.7.9
lombok
devtools
postgresql 14.1
Gladle
Thymeleaf
BoardController.java
/* 첨부 참조 */
/**
* <pre>
* 게시글 리스트 페이지
* </pre>
*
* @return
*/
@GetMapping("/list/page")
public String findAllOfPage(@ModelAttribute("params") final BoardSearchDto params,
@PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC) Pageable pageable,
Model model) {
// page : 페이지
// size : record의 갯수
model.addAttribute("result", boardService.findAllOfPage(params, pageable));
return "post/list";
}
/**
* <pre>
* 게시글 리스트 페이지
* </pre>
*
* @return
*/
@GetMapping("/list/page2")
public String findAllOfPage2(@ModelAttribute("params") final BoardSearchDto params,
Model model) {
PageRequest pageRequest = PageRequest.of(params.getPage(), params.getRecordSize(), Sort.by(Sort.Direction.DESC, "id"));
model.addAttribute("result", boardService.findAllOfPage2(params, pageRequest));
return "post/list";
}
BoardService.java
/* 첨부 참조 */
@Transactional(readOnly = true) // 선언적 트랜잭션을 사용
public PagingListResult<BoardDto> findAllOfPage(BoardSearchDto params, Pageable pageable) {
log.info("Offset :: {}", pageable.getOffset());
log.info("PageSize :: {}", pageable.getPageSize());
log.info("PageNumber :: {}", pageable.getPageNumber());
List<BoardDto> boardList = null;
Page<TBoardM> list = boardRepository.findAll(pageable);
log.debug("board :: {}", list);
if (list.hasContent()) {
boardList = list.stream().map(data -> new BoardDto(data))
.collect(Collectors.toList());
List<TBoardM> board = list.getContent(); // 검색된 데이터
int totalPage = list.getTotalPages(); // 전체 페이지 수
boolean hasNext = list.hasNext(); // 다음 페이지 존재여부
int totalCnt = (int)list.getTotalElements(); // 검색된 전체 건수
boolean isData = list.hasContent(); // 검색된 자료가 있는가?
log.info("board :: {}", board);
log.info("totalPage :: {}", totalPage);
log.info("hasNext :: {}", hasNext);
log.info("totalCnt :: {}", totalCnt);
log.info("isData :: {}", isData);
// Pagination 객체를 생성해서 페이지 정보 계산 후 SearchDto 타입의 객체인 params에 계산된 페이지 정보 저장
Pagination pagination = new Pagination(totalCnt, params);
params.setPagination(pagination);
return new PagingListResult<>(boardList, pagination);
} else {
return new PagingListResult<>(Collections.emptyList(), null);
}
}
@Transactional(readOnly = true) // 선언적 트랜잭션을 사용
public PagingListResult<BoardDto> findAllOfPage2(final BoardSearchDto params, PageRequest pageRequest) {
log.info("Offset :: {}", pageRequest.getOffset());
log.info("PageSize :: {}", pageRequest.getPageSize());
log.info("PageNumber :: {}", pageRequest.getPageNumber());
List<BoardDto> boardList = null;
Page<TBoardM> list = boardRepository.findAll(pageRequest);
log.debug("board :: {}", list);
if (list.hasContent()) {
boardList = list.stream().map(data -> new BoardDto(data))
.collect(Collectors.toList());
List<TBoardM> board = list.getContent(); // 검색된 데이터
int totalPage = list.getTotalPages(); // 전체 페이지 수
boolean hasNext = list.hasNext(); // 다음 페이지 존재여부
int totalCnt = (int)list.getTotalElements(); // 검색된 전체 건수
boolean isData = list.hasContent(); // 검색된 자료가 있는가?
log.info("board :: {}", board);
log.info("totalPage :: {}", totalPage);
log.info("hasNext :: {}", hasNext);
log.info("totalCnt :: {}", totalCnt);
log.info("isData :: {}", isData);
// Pagination 객체를 생성해서 페이지 정보 계산 후 SearchDto 타입의 객체인 params에 계산된 페이지 정보 저장
Pagination pagination = new Pagination(totalCnt, params);
params.setPagination(pagination);
return new PagingListResult<>(boardList, pagination);
} else {
return new PagingListResult<>(Collections.emptyList(), null);
}
}
Pagination.java
package octopus.base.model;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
public class Pagination {
private long totalRecordCount; // 전체 데이터 수
private int totalPageCount; // 전체 페이지 수
private int startPage; // 첫 페이지 번호
private int endPage; // 끝 페이지 번호
private int limitStart; // LIMIT 시작 위치
private boolean existPrevPage; // 이전 페이지 존재 여부
private boolean existNextPage; // 다음 페이지 존재 여부
public Pagination(long totalRecordCount, SearchDto params) {
if (totalRecordCount > 0) {
this.totalRecordCount = totalRecordCount;
calculation(params);
}
}
private void calculation(SearchDto params) {
// 전체 페이지 수 계산
totalPageCount = (int) ((totalRecordCount - 1) / params.getRecordSize()) + 1;
log.debug("totalPageCount :: {}", totalPageCount);
// 현재 페이지 번호가 전체 페이지 수보다 큰 경우, 현재 페이지 번호에 전체 페이지 수 저장
if (params.getPage() > totalPageCount) {
params.setPage(totalPageCount);
}
// 첫 페이지 번호 계산
startPage = ((params.getPage() - 1) / params.getPageSize()) * params.getPageSize() + 1;
// 끝 페이지 번호 계산
endPage = startPage + params.getPageSize() - 1;
// 끝 페이지가 전체 페이지 수보다 큰 경우, 끝 페이지 전체 페이지 수 저장
if (endPage > totalPageCount) {
endPage = totalPageCount;
}
// LIMIT 시작 위치 계산
limitStart = (params.getPage() - 1) * params.getRecordSize();
// 이전 페이지 존재 여부 확인
existPrevPage = startPage != 1;
// 다음 페이지 존재 여부 확인
existNextPage = (endPage * params.getRecordSize()) < totalRecordCount;
}
}
SearchDto.java
package octopus.base.model;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.Data;
/**
* <pre>
* </pre>
*/
@Data
@MappedSuperclass // BaseEntity를 상속한 Entity들은 아래의 필드들을 컬럼으로 인식한다.
@EntityListeners(AuditingEntityListener.class) // Audting(자동으로 값 Mapping) 기능 추가
public class SearchDto {
protected int page; // 현재 페이지 번호
protected int recordSize; // 페이지당 출력할 데이터 개수
protected int pageSize; // 화면 하단에 출력할 페이지 사이즈
protected Pagination pagination; // 페이지네이션 정보
public SearchDto() {
this.page = 0;
this.recordSize = 10;
this.pageSize = 10;
}
}
PagingListResult.java
package octopus.base.model;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@Getter
public class PagingListResult<T> {
private List<T> list = new ArrayList<>();
private Pagination pagination;
public PagingListResult(List<T> list, Pagination pagination) {
this.list.addAll(list);
this.pagination = pagination;
}
}
Spring Boot Start 옵션 : Run Configuration >> Spring Boot App >> Arguments Tab > VM arguments > -Dspring.profiles.active=local

목록 화면

페이지 이동

JPA에서 Page 기능을 사용하면서 알게된 내용이 있습니다.
첫번째. page 는 0 부터 시작합니다. ( 화면에서 Parameter 전달 시 page - 1 해야 합니다. )
두번째. 기본적으로 Count 쿼리를 수행합니다.
INFO 23-04-25 14:22:745[http-nio-9090-exec-1] [1;35m[▶ p6spy.logSQL ◀][0;39m[60]: - [statement] | 1 ms |
select
tboardm0_.id as id1_0_,
tboardm0_.crt_dt as crt_dt2_0_,
tboardm0_.crt_id as crt_id3_0_,
tboardm0_.mdf_dt as mdf_dt4_0_,
tboardm0_.mdf_id as mdf_id5_0_,
tboardm0_.contents as contents6_0_,
tboardm0_.notice_yn as notice_y7_0_,
tboardm0_.read_cnt as read_cnt8_0_,
tboardm0_.title as title9_0_
from
t_board_m tboardm0_
order by
tboardm0_.id desc limit 10 offset 10
Connection ID:7 | Excution Time:1 ms
Excution Time:1 ms
Call Stack :
--------------------------------------
INFO 23-04-25 14:22:749[http-nio-9090-exec-1] [1;35m[▶ p6spy.logSQL ◀][0;39m[60]: - [statement] | 0 ms |
select
count(tboardm0_.id) as col_0_0_
from
t_board_m tboardm0_
Connection ID:7 | Excution Time:0 ms
Excution Time:0 ms
Call Stack :
세번째. Pageable 을 사용하면 Controller 에서 Parameter 로 받을 수 있습니다.
/**
* # 1 페이지 조회를 요청합니다.
* http://localhost:9090/bbs/list/page?page=1
* # 2 페이지, 10개 Record 조회를 요청합니다.
* http://localhost:9090/bbs/list/page?page=1&size=10
* # 3 페이지, 10개 Record, id를 DESC 조회를 요청합니다.
* http://localhost:9090/bbs/list/page?page=1&size=10&sort=id.DESC
*/
@GetMapping("/list/page")
public String findAllOfPage(@ModelAttribute("params") final BoardSearchDto params,
@PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC) Pageable pageable,
Model model) {
// page : 페이지
// size : record의 갯수
model.addAttribute("result", boardService.findAllOfPage(params, pageable));
return "post/list";
}
네번째. 개발자에 의해 Pagination을 정의하기에는 Pageable 보다는 PageReqeust 를 사용하는 것이 좋을 것으로 보여집니다.
@GetMapping("/list/page2")
public String findAllOfPage2(@ModelAttribute("params") final BoardSearchDto params,
Model model) {
PageRequest pageRequest = PageRequest.of(params.getPage(), params.getRecordSize(), Sort.by(Sort.Direction.DESC, "id"));
model.addAttribute("result", boardService.findAllOfPage2(params, pageRequest));
return "post/list";
}
참고1. 페이지를 유지하면서 엔티티를 DTO 로 변환하기
Page<TBoardM> page = boardRepository.findAll(pageRequest);
Page<BoardDto> dtoPage = page.map(m -> new BoardDto());
참고2. Page의 구현체 PageImpl
public Page<BoardDto> findAllOfPage(Pageable pageable) {
Page<TBoardM> list = boardRepository.findAll(pageable);
List<BoardDto> boardList = new ArrayList<>();
BoardDto dto = null;
for (TBoardM board : list) {
dto = new BoardDto(board);
boardList.add(dto);
}
return new PageImpl<>(boardList, pageable, list.getTotalElements());
}
REST API URI 명명 규칙에는 맞지 않지만, 일단 테스트라는 명목으로 적당히 무시해서 URI를 정의했습니다.
https://dzone.com/articles/7-rules-for-rest-api-uri-design-1
7 Rules for REST API URI Design - DZone
URIs, or Uniform Resource Identifiers, should be designed to be readable and clearly communicate the API resource model. These rules will help you succeed.
dzone.com
https://dkrnfls.tistory.com/218?category=1026591
REST API URI를 설계하는 7가지 규칙
최근 백엔드를 구현하던 중 uri를 규칙없이 작성하는 것 같아 여러글을 찾아보다가 잘 쓰여진 글이 있는 것 같아 가져가고 싶었습니다. 해당 포스트는 아래에 첨부된 사이트를 번역한 글입니다.
dkrnfls.tistory.com
https://congsong.tistory.com/26
스프링 부트(Spring Boot) - 페이징(Paging) & 검색(Search) 처리하기 1/2 [Thymeleaf, MariaDB, IntelliJ, Gradle, MyBat
본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다. DBMS 툴은 DBeaver를 이용하며, DB는 MariaDB를 이용합니다. (MariaDB 설치하기) 화면 처리는 HTML5 기반
congsong.tistory.com
https://hermeslog.tistory.com/704
[Spring Boot] 게시판 #1 - 개발환경
게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다. 개발환경 STS 4.17.2.RELEASE OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) Spring Boot 2.7.9 lombok devtools postgresql 14.1 Gladle Thymeleaf
hermeslog.tistory.com
https://hermeslog.tistory.com/706
[Spring Boot] 게시판 #2 - 간단한 게시판 CRUD 개발
게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다. https://hermeslog.tistory.com/704 [Spring Boot] 게시판 #1 - 개발환경 게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다
hermeslog.tistory.com
https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/
Pageable을 이용한 Pagination을 처리하는 다양한 방법
Spring Data JPA에서 Pageable 를 활용한 Pagination 의 개념과 방법을 알아본다.
tecoble.techcourse.co.kr
'Spring Boot Framework' 카테고리의 다른 글
[Spring Boot] 게시판 #4 - 게시판 + 댓글 (0) | 2023.05.02 |
---|---|
[Spring Boot] JPA에서 Boolean 처리 ( @Converter, @Convert ) (0) | 2023.04.25 |
[Spring Boot] 게시판 #2 - 간단한 게시판 CRUD 개발 (0) | 2023.04.23 |
[Spring Boot] 게시판 #1 - 개발환경 (0) | 2023.04.14 |
[Spring Boot] HandlerMethodArgumentResolver를 이용한 중복 Source 제거 방법 (0) | 2023.04.04 |