inblog logo
|
keepgoing
    SpringBoot

    [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으로 컨버팅하면 이런일은 안일어난다..ㅎ
    [JPA] Lazy 로딩으로 인한 JSON 반환 오류 (No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer)
    1. 문제의 상황 " data-ke-type="html"> HTML 삽입 미리보기할 수 없는 소스 Issue 와 Comment 는 1:N의 관계이다. 따라서 Comment의 코드를 보면 다음과 같이 Lazy 로딩이 걸려있다. ▶ Comment @Getter @Entity @EqualsAndHashCode(of = "id") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Comment extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "comment_id") private Long id; @ManyToOne(fetch ..
    [JPA] Lazy 로딩으로 인한 JSON 반환 오류 (No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer)
    https://blogshine.tistory.com/548
    [JPA] Lazy 로딩으로 인한 JSON 반환 오류 (No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer)
    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 걸어버린다.
    ( → 테스트 끝나면 지우기 )
     
    JPA활용2_오류(No serializer found)
    해결 방법application.yml 또는 application.properties에 추가에러 나는 엔티티 설정 Lazy - > EAGER로 변경jpabook.jpashop.domain.Order"member" -> @ManyToOne(fetch = EAGER)jpa
    JPA활용2_오류(No serializer found)
    https://velog.io/@easthyun/JPA활용2오류No-serializer-found
    JPA활용2_오류(No serializer found)
    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
    Online JSON Viewer and Formatter
    JSON Viewer and Formatter - Convert JSON Strings to a Friendly Readable Format
    Online JSON Viewer and Formatter
    https://jsonviewer.stack.hu/
    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
     
    [JPA] Spring Data Jpa + Pageable로 Pagination 쉽게 구현하기
    이번주는 독서리뷰를 남길 수 있는 미니 프로젝트를 진행하고 있다. 리뷰 글을 특정 조건으로 뽑아서 정렬 기준을 선택하여 조회하는 기능을 구현하고자 한다.전체 리뷰글을 조회하거나, 내가 좋아요를 누른 리뷰글만 조회하거나, 내가 쓴 리뷰글만 조회하거나, 원하는 카테고리의
    [JPA] Spring Data Jpa + Pageable로 Pagination 쉽게 구현하기
    https://velog.io/@kimdy0915/JPA-Spring-Data-Jpa-Pageable로-Pagination-쉽게-구현하기
    [JPA] Spring Data Jpa + Pageable로 Pagination 쉽게 구현하기
    Share article

    keepgoing

    RSS·Powered by Inblog