일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- Docker
- 문서
- maven
- myBatis
- STS
- JavaScript
- Source
- git
- Eclipse
- SpringBoot
- Exception
- Spring Boot
- Thymeleaf
- Open Source
- spring
- error
- JDBC
- Tomcat
- AJAX
- MSSQL
- PostgreSQL
- 설정
- oracle
- Core Java
- 오픈소스
- IntelliJ
- Python
- ubuntu
- jpa
- MySQL
- Today
- Total
헤르메스 LIFE
[Spring Boot] 게시판 #3 - 게시판 목록 + 페이징처리 본문
게시판을 작성해보려 합니다. 조금씩 살을 붙여나가 보려고 합니다.
게시판 목록의 디자인 및 일부 소스는 도뎡님의 허락을 받아 사용했습니다.
https://congsong.tistory.com/26
개발환경
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
https://dkrnfls.tistory.com/218?category=1026591
https://congsong.tistory.com/26
https://hermeslog.tistory.com/704
https://hermeslog.tistory.com/706
https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/
'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 |