Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- Proxy
- Servlet
- JDBC
- Exception
- Android
- 스프링
- 김영한
- http
- 자바
- jpa
- SpringBoot
- 백준
- Spring Boot
- 인프런
- spring
- 스프링 핵심 기능
- pointcut
- QueryDSL
- java
- JPQL
- AOP
- 스프링 핵심 원리
- Thymeleaf
- 알고리즘
- transaction
- kotlin
- Greedy
- db
- 그리디
- springdatajpa
Archives
- Today
- Total
개발자되기 프로젝트
Paging처리 본문
1. Paging 처리 - Repository
- JPA를 사용하면 Paging처리가 간단한다.
- setFirstResult : 어디서부터 시작?
- seMaxResults : 한 페이지에 몇개??
public List<Post> findAllPaging(int offset, int limit){
return em.createQuery("select p from Post p order by p.id asc", Post.class)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
2. Paging 구현 - Page
- Page List를 담고있는 List를 사용할 예정.
@Data
public class Page {
private List<List<Post>> pages = new ArrayList<>();
}
3. Paging 구현 - PostService
- post 전체 개수를 / 한 페이지당 수 로 나눔.
- 만약 나머지가 0이 아니면 한 페이지가 더 있어야 하기 때문에 cnt++
- cnt수 만큼 반복문을 돌면서 Page에 pages를 추가함.
- 추가한 위 offset에 limit만큼 더해줌.
@Override
public Page findAllPaging(int offset, int limit) {
int size = findAll().size();
int cnt = size/limit;
if(size%limit != 0){
cnt++;
}
Page page = new Page();
for (int i=0;i<cnt; i++){
List<Post> results = postRepository.findAllPaging(offset, limit);
page.getPages().add(results);
offset = offset+limit;
}
return page;
}
4. Test
- 테스트용 데이터는 많지 않으니 2개 씩 끊어보자.
- 데이터는 7개니 2개씩 끊으면 총 4페이지가 나온다.
@Test
void findAllPageTest(){
Page page = postService.findAllPaging(0, 2);
List<List<Post>> pages = page.getPages();
for (List<Post> posts : pages) {
System.out.println("===================");
for (Post post : posts) {
System.out.println("post title : " + post.getTitle());
}
System.out.println("===================");
}
assertThat(page.getPages().size()).isEqualTo(4);
assertThat(page.getPages().get(0).get(0).getTitle()).isEqualTo("title1");
}
5. 점검
- 생각해 보니 이 방법은 좋지 않다.
- 왜냐? 한번에 모든 post를 불러오기 때문.
- 내가 원하는 방법은 page 버튼을 눌렀을 때 바로바로 해당 page만 불러오는 것이다.
6. PostService
- postService에서는 단순히 repository를 호출하는 역할만 한다.
@Override
public List<Post> findAllPaging(int offset, int limit) {
return postRepository.findAllPaging(offset, limit);
}
7. Controller - 글목록 조회
- offset, limit은 고정하자.
- Controller 가 빈에 올라가면 가장 먼저 전체 post의 수를 가져와서 page수 계산해둠.
- 이후 controller에서 page번호를 queryParameter로 받자.
- page 를 QueryParam으로 받으면 경우에 따라 처리를 해주자.
- 처음 글 목록을 조회하면 QueryParam이 붙지 않는다. 이 때는 1페이지를 보여주자.
- page 번호를 사용해서 service를 호출할 때 offset을 page번호에 따라 변경해 주자.
@Slf4j
@Controller
@RequestMapping("/posts")
@RequiredArgsConstructor
public class PostFormController {
...
private int offset = 0;
private int limit = 10;
private int totalSize;
private int totalSizeSearch;
private int numberOfPages;
private int numberOfSearchPages;
@PostConstruct
public void init(){
totalSize = postService.findAll().size();
numberOfPages = getNumberOfPages();
}
...
@GetMapping
public String pages(@RequestParam("page") @Nullable Integer page, Model model, HttpServletRequest request, HttpServletResponse response) {
MemberDto loginMemberDto = getLoginMember(request);
if (page == null){
page = 1;
}else if (page <= 0 ){
throw new IllegalArgumentException("page 번호는 음수일 수 없음");
}else if (page > numberOfPages){
throw new IllegalArgumentException("존재하지 않는 페이지 입니다.");
}
List<Post> posts = postService.findAllPaging(offset + (page - 1) * limit, limit);
List<PostDto> postsDto = getPostDtos(posts);
loginMemberDto = checkNonLoginGuest(loginMemberDto);
model.addAttribute("member", loginMemberDto);
model.addAttribute("posts", postsDto);
model.addAttribute("numberOfPages", numberOfPages);
model.addAttribute("searchForm", new SearchForm());
model.addAttribute("status", SearchStatus.statusNormal);
return "post/posts";
}
...
private int getNumberOfPages() {
int numberOfPages = totalSize/limit;
if(numberOfPages == 0){
numberOfPages = 1;
}else {
if (totalSize%limit != 0){
numberOfPages++;
}
}
return numberOfPages;
}
}
8. Controller - 글 저장
- 추가로 글을 작성하고 나면 post의 수가 변경된다. post를 저장하고 나면 init()한번 더 실행해주자.
@PostMapping("/new/form")
public String addPost(@ModelAttribute("post") PostDto postDto, HttpServletRequest request) {
MemberDto loginMemberDto = getLoginMember(request);
if (hasNoAuthority(loginMemberDto)) return "redirect:/posts";
Member findMember = memberService.findMemberById(loginMemberDto.getId());
log.info("post1.title : {}", postDto.getTitle());
log.info("post1.content : {}", postDto.getContent());
Post post = new Post().dtoToPost(postDto);
post.setMember(findMember);
postService.savePost(post);
memberService.joinMember(findMember);
log.info("new post.title = {}", post.getTitle());
log.info("new post.content = {}", post.getContent());
log.info("new post by : {}", loginMemberDto.getUserId());
init();
return "redirect:/posts";
}
9. Controller - 글 검색
- 글 검색의 경우 글목록을 조회하는 것과 유사하다.
- 다만, page 번호를 산출하는 기준이 달라진다.
- 전체 post의 수가 아니라 검색 결과를 기준으로 결정된다.
- 하지만 page번호를 누를 때 마다 쿼리를 한번 더 날리는게...마음에 들지 않는다...다른 방법이 없을깡..
- 생각해보니 어차피 한 번 검색한 이후로 1차캐시에서 가져오니 문제는 없다.
@GetMapping("/search")
public String searchPaging(@RequestParam("keyword") String keyword, @RequestParam("page") @Nullable Integer page,
Model model, HttpServletRequest request) throws NoResultException {
if (page == null){
page = 1;
}else if (page <= 0 ){
throw new IllegalArgumentException("page 번호는 음수일 수 없음");
}else if (page > numberOfPages){
throw new IllegalArgumentException("존재하지 않는 페이지 입니다.");
}
log.info("keyword: {}", keyword);
MemberDto loginMemberDto = getLoginMember(request);
//전체 size구하기 위해서 쿼리를 한번 더 날리는게 마음에 안듦..
totalSizeSearch = postService.findByTitleContains(keyword).size();
List<Post> posts = postService.findByTitleContainsPaging(keyword,offset + (page - 1) * limit, limit);
List<PostDto> postsDto = getPostDtos(posts);
numberOfSearchPages = totalSizeSearch/limit;
if(numberOfSearchPages == 0){
numberOfSearchPages=1;
}else {
if (totalSizeSearch%limit != 0){
numberOfSearchPages++;
}
}
log.info("number of pages : {}", numberOfSearchPages);
loginMemberDto = checkNonLoginGuest(loginMemberDto);
model.addAttribute("member", loginMemberDto);
model.addAttribute("posts", postsDto);
model.addAttribute("searchForm", new SearchForm(keyword));
model.addAttribute("status", searchStatus);
model.addAttribute("numberOfPages", numberOfSearchPages);
return "post/posts";
}
10. View - posts.html
- Bootstrap에서 pagination을 가지고 왔다.
- thymeleaf에서 for문과 유사하에 사용하도록 #numbers.sequence(from, to)를 사용했다.
- th:if를 사용하여 검색일 경우 아닐경우 페이지 번호 출력을 구분했다.
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li th:if="${status}!='search'"
th:each = "n: ${#numbers.sequence(1,numberOfPages)}" class="page-item">
<a class="page-link" th:href="@{|/posts?page=${n}|}" th:text="${n}">일반 페이지번호</a></li>
<li th:if="${status}=='search'"
th:each = "n: ${#numbers.sequence(1,numberOfPages)}" class="page-item">
<a class="page-link" th:href="@{|/posts/search?keyword=${searchForm.keyword}&page=${n}|}" th:text="${n}">검색시 페이지번호</a></li>
</ul>
</nav>
11. 결과
- 글목록을 조회하면 페이징 처리가 된다.
- 검색결과도 페이징이 가능하다.
- 어...근데...음...글을 새로 저장해도 보이지가 않는다...DB에는 들어갔는데 ㅜㅜ
- 내림차순으로 정렬하니까 잘보인다 ㅋㅋ
12.앞으로
est용 data 서버시작할 때 DB에 올리기Listener
createdAt, updatedAt등
게시글에 작성자 표시view 정리게시글 검색- 검색 옵션 추가 : 작성자, 제목 구분
영속성 전이 설정게시글 삭제회원 탈퇴
댓글 삭제 버튼글 수정 기능 추가- 대댓글
- 검증 & 예외처리
회원 가입시 필수 정보 지정.특수문자, 공백 검증 등(email 형식?)- Spring제공 Validator로 변경.
인증처리로그인한 사용자만 글 & 댓글 작성 가능.본인이 작성한 글, 댓글만 수정/삭제 가능관리자는 모든 권한
- 오류 화면
페이징 처리버그 수정
- 컬렉션 조회 최적화
13. GitHub: 211021 Paging
'Project > 블로그 게시판 만들기' 카테고리의 다른 글
id 생성전략 변경 (0) | 2021.10.22 |
---|---|
Exception Resolver, 예외 페이지 (0) | 2021.10.21 |
비로그인 사용자 글 등록/삭제/수정 test (0) | 2021.10.19 |
@AutoConfigureMockMvc, PostController Test (0) | 2021.10.19 |
MockMVC (0) | 2021.10.18 |
Comments