Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

Paging처리 본문

Project/블로그 게시판 만들기

Paging처리

Seung__ 2021. 10. 21. 21:56

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


 

GitHub - bsh6463/blog

Contribute to bsh6463/blog development by creating an account on GitHub.

github.com

 

Comments