[Springboot] 25 페이징 처리

김호정's avatar
Sep 17, 2024
[Springboot] 25 페이징 처리
 
notion image
페이징을 위한 mFindAll 완성
 
notion image
테스트 코드 작성 후 돌려보기.
 
위의 select는 board 객체에서 정보를 가지고 오는 것이고
아래의 select는 페이징에 필요한 계산을 위해 count를 들고 오는 쿼리다.
→ Pageable 로 페이징하면 이렇게 2번 쿼리가 실행된다.
 
그리고
 
이 Page 객체안에 뭐가 있는지 궁금하면 JSON으로 바꿔서 확인하는게 제일 빠른 방법이다!
 
notion image
ObjectMapper를 사용해서
 
bytebuddy.ByteBuddyInterceptor →
비어있는애가 getter를 때렸는데 select 해서 결과를 받기전에 json으로 직렬화하고 있을때 나오는 에러 ( 레이지 전략이어서 터지는 거 )
 
DTO로 옮기고 JSON으로 컨버팅하면 이런일은 안일어난다..ㅎ
notion image
 
JSON 컨버팅할때 NULL이면 원래 터트리는데 .
 
SELECT 해서 아직 응답을 받기전이라 NULL
 
옵션을 여러개 주면 더 좋은데 1개만 달 수 있다. 그러니 1개 달자
 
notion image
application.properties 에 FailOnEmpty false로 설정
 
그래도 같은 에러 뜸
notion image
notion image
끝까지 복사해서 구글에 검색해보기
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.domain.PageImpl["content"]->java.util.Collections$UnmodifiableRandomAccessList[0]->org.example.springv3.board.Board["replies"]->org.hibernate.collection.spi.PersistentBag[0]->org.example.springv3.reply.Reply["user"]->org.example.springv3.user.User$HibernateProxy$dPMx5bgx["hibernateLazyInitializer"])
 
 
직렬화 안하고 싶은 애 한테 ignore 걸어버린다.
( → 테스트 끝나면 지우기 )
 
notion image
→ USER를 조회 안하고 잘 나온다.
 
{"content":[{"id":5,"title":"제목5","content":"내용5","createdAt":1726101930062},{"id":4,"title":"제목4","content":"내용4","createdAt":1726101930062},{"id":3,"title":"제목3","content":"내용3","createdAt":1726101930061}],"pageable":{"pageNumber":0,"pageSize":3,"sort":{"empty":true,"sorted":false,"unsorted":true},"offset":0,"unpaged":false,"paged":true},"last":false,"totalPages":2,"totalElements":5,"first":true,"size":3,"number":0,"sort":{"empty":true,"sorted":false,"unsorted":true},"numberOfElements":3,"empty":false}
JSONVIEWER
notion image
notion image
콘솔에서 복사해서 TEXT에 넣고
 
notion image
VIEWER로 확인 !
 
notion image
테스트할 때 제일 빠른방법!!!!
 
완전한 해결방법은 DTO를 만드는건데 TEST에서 객체안의 값을 확인하고 싶을 때 이 방법을 사용!!!!
 
 
  • JSONIgnore는 테스트 할 때만 쓰는거지, 나중에 배포할 때는 다 지워야 한다.
  • RepositoryTest는 신경안써도 됨! 나중에 통합테스트 할 거니까
 
 
 
이제 DTO로 하결하기 위해서
notion image
IGNORE를 제거해준다. (주석처리)
 
 
 
객체를 그냥 호출하면 ToString이 발동한다.
 
서로 호출해서 터진다.
 
ToString에서 reply에서 board 호출하는걸 지우면 toString 안터진다.
 
꼭 json으로 안봐도 toString으로 볼 수도 있다.
 
예쁘게 파싱하려면 json으로 하는게 낫다.
 
notion image
 
여기 Data를 안쓰고 setter, getter를 만드는 이유는 toString을 내가 커스터마이징해서
데이터 볼때 수정해야하기 때문!
 
 
notion image
notion image
notion image
toString으로 조회하는법
 
 
 
notion image
서비스에서 Sort → Pageable로 리팩토링
 
notion image
이 부분 지워도됨. pageable이 해주니까!
 
 
notion image
 
이 부분은 mustache에 내가 직접 뿌릴 거니까 (Getter를 선별해서 뿌릴거니까)
DTO 안만들어도 됨
 
page * 3은 offset으로 시작위치임!
pasesize는 갯수
 
notion image
defaultValue를 설정해주면 아래 코드는 안써줘도 된다.
더욱 깔끔
 
notion image
 
return boardPG 하면 다 터진다.
 
 
DTO를 안만들어도 내가 있는 것만 꺼낼거니까 안터진다.
 
아까봤듯이 CONTENT안에 다 있었으니까
 
models → model.content 로 반복문 돌리도록 수정
 
 
notion image
 
아래 디자인 코드도 추가
 
<ul class="pagination d-flex justify-content-center"> <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li> </ul>
 
previous/ next 가 나온다.
 
 
notion image
 
 
 
notion image
그냥 페이지로 수정
 
 
notion image
1 해주면 잘 나온다.
 
 
notion image
 
일단 강제로 적어준다
 
(앞에 localhost:8080이 있음)
 
disable하면 비활성화되니까 disable도 지워준다.
 
 
 
notion image
notion image
notion image
 
 
notion image
이런 연산이 안되는게 mustache,,,
 
비즈니스 연산은 컨트롤러에서 해와라
notion image
컨트롤러에서 비즈니스 연산을 다 해야해서
prev누르면 나올거랑 next 넣으면 나올거 연산해 간다.
notion image
넣어줌
 
a 태그 오류나는건 버그니까 신경 ㄴ
 
notion image
notion image
 
 
notion image
NEXT 누르면 연산이 돼서 계속 올라가는 것을 확인할 수 있다.
 
notion image
 
 
notion image
여기있는거 for문 써서 DTO에 옮기면 됨!
 
 
이렇게 왜 해야하느냐
  1. mustache 여서
  1. MVC → 컨트롤러에 비즈니스 로직이 있으면 컨트롤러의 책임이 뭐에요 ?
물어볼 수 있다.
 
무조건 dto로 바꾸는게 아니다.
 
직접 뿌릴거 ( → no session 이런거 안터짐 ) 는 DTO에 넣을 필요가 없다.
 
 
  • 직접 뿌린다는게
 
notion image
 
이렇게 model이 아닌 key가 prev, next 인 request객체에 담아와서 prev, next로 바로 뿌리는 걸 의미한다.
 
DTO에 담아오면 model.prev 혹은 models.prev 이런식으로 뿌리는 것.
 
 
자 그럼 이제 DTO에 담아오는 것으로 리팩토링 해보자
 
 
 
 
notion image
 
일단 이 안에 있는 것 중에 필요한 것을 담아야 한다. (지금 있는건 쌤이 준 예시)
 
notion image
 
DTO에 뭐 담아야 하는지 ( → 페이징 할 때 어떤 정보가 필요한지 ) 파악하고 이제 BoardResponse에
DTO를 만들자.
 
객체 배열로 넘어오는 저 content를 List<Content>로 받아야 한다고 했는데
지금 만들 DTO에서 List<Content>로 받을 건 저 정보가 아니라,
우리 Board 객체의 id 와 title이다 ( → list 페이지에서 필요한 것들 )
 
이미 그렇게 써놔서 Board가 들어가 있음!
 
DTO 이름은 PageDTO로 해서 작성해보자
 
@Data public static class PageDTO { private Integer number; // 현재페이지 private Integer totalPage; // 전체페이지 개수 private Integer size; // 한페이지에 아이템 개수 private Boolean first; private Boolean last; private Integer prev; // 현재페이지 -1 private Integer next; // 현재페이지 +1 private List<Content> contents = new ArrayList<>(); @Data class Content { private Integer id; private String title; } }
 
선생님이 짜 준 틀.
 
현재 페이지, 전체페이지 개수, 한 페이지에 아이템 개수, 첫번째 페이지 여부, 마지막 페이지 여부,
prev, next ( → 이전에 request에 담아서 보냈던거 DTO에 담아서 보내자 )
contents ( 이 안에서 필요한건 Board의 id랑 title )
 
 
@Data public static class PageDTO { private Integer number; // 현재페이지 private Integer totalPage; // 전체페이지 개수 private Integer size; // 한페이지에 아이템 개수 private Boolean first; private Boolean last; private Integer prev; // 현재페이지 -1 private Integer next; // 현재페이지 +1 private List<Content> contents = new ArrayList<>(); public PageDTO(Page<Board> boardPG){ this.number = boardPG.getNumber(); this.totalPage = boardPG.getTotalPages(); this.size = boardPG.getSize(); this.first = boardPG.isFirst(); this.last = boardPG.isLast(); this.prev = boardPG.getNumber() - 1; this.next = boardPG.getNumber() + 1; for(Board board : boardPG.getContent()){ contents.add(new Content(board)); } } @Data class Content { // 엔티티가 아니니까 DTO 붙여주지 말기 private Integer id; private String title; public Content(Board board) { this.id = board.getId(); this.title = board.getTitle(); } } }
boardPG에 있는 Number, TotalPages, Size, First, Last 를 꺼내와서 써주고,
prev 를 현재페이지 - 1, next를 현재페이지 + 1 로 설정해준다.
 
그리고 Content 안에 객체배열로 들어있는 Board 오브젝트들을 반복문을 통해 꺼내주자.
 
그리고 Content 타입으로 바꿔서 (아래 calss Content에서 출신을 워싱해줌) ArratList에 넣어줌
 
PageDTO 완성했으니 이제 Service에서 PageDTO 를 return 하도록 수정하자.
 
public BoardResponse.PageDTO 게시글목록보기(String title, int page) { Pageable pageable = PageRequest.of(page, 3, Sort.Direction.DESC, "id"); if(title == null){ Page<Board> boardList = boardRepository.findAll(pageable); // findAll안에 pageable을 넣을 수 있게 되어있음 // pageable 객체가order by b.id desc 도 해줘서 레파지 return new BoardResponse.PageDTO(boardList); }else{ Page<Board> boardList = boardRepository.mFindAll(title, pageable); // 동적쿼리 아님 return new BoardResponse.PageDTO(boardList); } }
 
기존에 boardPG를 return 하던 코드에서 BoardResponse.PageDTO 를 return 하도록 수정함!
 
 
자 그럼 이제 컨트롤러도 수정하자
 
@GetMapping("/") public String list(@RequestParam(name = "title", required = false) String title, @RequestParam(name = "page", required = false, defaultValue = "0") Integer page, HttpServletRequest request) { // ? 해서 들어오는 데이터는 @requestParam 써준다. 근데 해당 어노테이션은 생략할 수 있다. BoardResponse.PageDTO model = boardService.게시글목록보기(title, page); // 페이지 객체 request.setAttribute("model", model); // collection이 아니라 object니까 models -> model로 바꿔주기 (아까 눈으로 본 것) return "board/list"; }
 
게시글 목록보기에서 return 한걸 똑같이 BoardResponse.PageDTO 에 담아주고
return할 때 model 에 담아준다.
 
models 가 아니라 model인 이유는
아까 test에서 JsonIgnore 붙여서 확인한 JSON 데이터가 “객체” 이기 때문 !
(→ content는 배열이 맞지만 {}로 전체적으로 감싸져 있는게 JSON 객체임 )
 
{"content":[{"id":5,"title":"제목5","content":"내용5","createdAt":1726101930062},{"id":4,"title":"제목4","content":"내용4","createdAt":1726101930062},{"id":3,"title":"제목3","content":"내용3","createdAt":1726101930061}],"pageable":{"pageNumber":0,"pageSize":3,"sort":{"empty":true,"sorted":false,"unsorted":true},"offset":0,"unpaged":false,"paged":true},"last":false,"totalPages":2,"totalElements":5,"first":true,"size":3,"number":0,"sort":{"empty":true,"sorted":false,"unsorted":true},"numberOfElements":3,"empty":false}
 
notion image
 
 
notion image
notion image
 
 
암튼 model에 담아서 list로 보내게 되면 model에서 꺼내야 하니까 list.mustache 파일도 수정해준다
 
{{>layout/header}} <div class="container p-5"> <div class="d-flex justify-content-end mb-2"> <form action="/" method="get" class="d-flex col-md-3"> <input class="form-control me-2" type="text" placeholder="Search" name="title"> <button class="btn btn-primary">Search</button> </form> </div> {{#model.contents}} <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">{{title}}</h4> <a href="/board/{{id}}" class="btn btn-primary">상세보기</a> </div> </div> {{/model.contents}} <h1>{{model.number}}</h1> <!-- 현재 페이지 --> <ul class="pagination d-flex justify-content-center"> <li class="page-item"><a class="page-link" href="?page={{model.prev}}">Previous</a></li> {{#model.numbers}} <li class="page-item"><a class="page-link" href="?page={{.}}">{{.}}</a></li> {{/model.numbers}} <li class="page-item"><a class="page-link" href="?page={{model.next}}">Next</a></li> </ul> </div> {{>layout/footer}}
notion image
model에서 꺼내서 뿌리는 걸로 수정!
 
여기서 {{.}} 이 뭐지? 싶을 수 있는데, mustache 문법이다 !
 
notion image
→ numbers를 반복문으로 돌리고 있으니까 numbers
 
 
 
그리고 돌리면 아까 따로 담아서 보내 뿌렸던 거 처럼 페이징 잘 된다 : )
 
 
DTO에 담기 끝 !
 
 
 
 
notion image
 
Share article

keepgoing