
페이징을 위한 mFindAll 완성

테스트 코드 작성 후 돌려보기.
위의 select는 board 객체에서 정보를 가지고 오는 것이고
아래의 select는 페이징에 필요한 계산을 위해 count를 들고 오는 쿼리다.
→ Pageable 로 페이징하면 이렇게 2번 쿼리가 실행된다.
그리고
이 Page 객체안에 뭐가 있는지 궁금하면 JSON으로 바꿔서 확인하는게 제일 빠른 방법이다!

ObjectMapper를 사용해서
bytebuddy.ByteBuddyInterceptor →
비어있는애가 getter를 때렸는데 select 해서 결과를 받기전에 json으로 직렬화하고 있을때 나오는 에러 ( 레이지 전략이어서 터지는 거 )
DTO로 옮기고 JSON으로 컨버팅하면 이런일은 안일어난다..ㅎ

JSON 컨버팅할때 NULL이면 원래 터트리는데 .
SELECT 해서 아직 응답을 받기전이라 NULL
옵션을 여러개 주면 더 좋은데 1개만 달 수 있다. 그러니 1개 달자

application.properties 에 FailOnEmpty false로 설정
그래도 같은 에러 뜸


끝까지 복사해서 구글에 검색해보기
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 걸어버린다.
( → 테스트 끝나면 지우기 )

→ 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


콘솔에서 복사해서 TEXT에 넣고

VIEWER로 확인 !

테스트할 때 제일 빠른방법!!!!
완전한 해결방법은 DTO를 만드는건데 TEST에서 객체안의 값을 확인하고 싶을 때 이 방법을 사용!!!!
- JSONIgnore는 테스트 할 때만 쓰는거지, 나중에 배포할 때는 다 지워야 한다.
- RepositoryTest는 신경안써도 됨! 나중에 통합테스트 할 거니까
이제 DTO로 하결하기 위해서

IGNORE를 제거해준다. (주석처리)
객체를 그냥 호출하면 ToString이 발동한다.
서로 호출해서 터진다.
ToString에서 reply에서 board 호출하는걸 지우면 toString 안터진다.
꼭 json으로 안봐도 toString으로 볼 수도 있다.
예쁘게 파싱하려면 json으로 하는게 낫다.

여기 Data를 안쓰고 setter, getter를 만드는 이유는 toString을 내가 커스터마이징해서
데이터 볼때 수정해야하기 때문!



toString으로 조회하는법

서비스에서 Sort → Pageable로 리팩토링

이 부분 지워도됨. pageable이 해주니까!

이 부분은 mustache에 내가 직접 뿌릴 거니까 (Getter를 선별해서 뿌릴거니까)
DTO 안만들어도 됨
page * 3은 offset으로 시작위치임!
pasesize는 갯수

defaultValue를 설정해주면 아래 코드는 안써줘도 된다.
더욱 깔끔

return boardPG 하면 다 터진다.
DTO를 안만들어도 내가 있는 것만 꺼낼거니까 안터진다.
아까봤듯이 CONTENT안에 다 있었으니까
models → model.content 로 반복문 돌리도록 수정

아래 디자인 코드도 추가
<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 가 나온다.


그냥 페이지로 수정

1 해주면 잘 나온다.

일단 강제로 적어준다
(앞에 localhost:8080이 있음)
disable하면 비활성화되니까 disable도 지워준다.




이런 연산이 안되는게 mustache,,,
비즈니스 연산은 컨트롤러에서 해와라

컨트롤러에서 비즈니스 연산을 다 해야해서
prev누르면 나올거랑 next 넣으면 나올거 연산해 간다.

넣어줌
a 태그 오류나는건 버그니까 신경 ㄴ



NEXT 누르면 연산이 돼서 계속 올라가는 것을 확인할 수 있다.


여기있는거 for문 써서 DTO에 옮기면 됨!
이렇게 왜 해야하느냐
- mustache 여서
- MVC → 컨트롤러에 비즈니스 로직이 있으면 컨트롤러의 책임이 뭐에요 ?
물어볼 수 있다.
무조건 dto로 바꾸는게 아니다.
직접 뿌릴거 ( → no session 이런거 안터짐 ) 는 DTO에 넣을 필요가 없다.
- 직접 뿌린다는게

이렇게 model이 아닌 key가 prev, next 인 request객체에 담아와서 prev, next로 바로 뿌리는 걸 의미한다.
DTO에 담아오면 model.prev 혹은 models.prev 이런식으로 뿌리는 것.
자 그럼 이제 DTO에 담아오는 것으로 리팩토링 해보자

일단 이 안에 있는 것 중에 필요한 것을 담아야 한다. (지금 있는건 쌤이 준 예시)

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}



암튼 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}}

model에서 꺼내서 뿌리는 걸로 수정!
여기서 {{.}} 이 뭐지? 싶을 수 있는데, mustache 문법이다 !

→ numbers를 반복문으로 돌리고 있으니까 numbers
그리고 돌리면 아까 따로 담아서 보내 뿌렸던 거 처럼 페이징 잘 된다 : )
DTO에 담기 끝 !

Share article