- Spring Data JPA의 PagingAndSortingRepository 사용 PagingAndSortingRepository는 기본적으로 페이징과 정렬 기능을 지원하는 인터페이스입니다. Pageable 객체를 사용해 간단하게 페이징을 처리할 수 있습니다.
코드 예시
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
Page<User> findAll(Pageable pageable);
}
Pageable pageable = PageRequest.of(0, 10); // 페이지 번호 0, 페이지 당 10개 데이터
Page<User> page = userRepository.findAll(pageable);
List<User> users = page.getContent(); // 페이징된 데이터
- Spring Data JPA의 JpaRepository 사용 JpaRepository는 더 많은 기능을 제공하는 인터페이스로, 페이징을 포함한 여러 작업을 간편하게 할 수 있습니다. Page나 Slice를 반환하여 페이징된 데이터를 가져올 수 있습니다.
코드 예시
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByName(String name, Pageable pageable);
}
사용 예:
Pageable pageable = PageRequest.of(0, 10);
Page<User> page = userRepository.findByName("John", pageable);
List<User> users = page.getContent();
Page: 전체 페이지 수, 현재 페이지 번호, 총 데이터 수 등을 제공
Slice: 전체 페이지 수나 총 데이터 수 없이 다음 페이지가 있는지 여부만 제공
- JPQL과 EntityManager를 사용한 페이징 처리 직접 JPQL 쿼리를 사용해 페이징 처리를 할 수 있습니다. EntityManager의 createQuery() 메서드를 사용하여 페이징 쿼리를 실행합니다.
String jpql = "SELECT u FROM User u WHERE u.name = :name";
TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
query.setParameter("name", "John");
query.setFirstResult(0); // 시작 인덱스 (0부터 시작)
query.setMaxResults(10); // 한 번에 가져올 데이터 수
List<User> users = query.getResultList();
- Native Query를 사용한 페이징 처리 네이티브 SQL 쿼리를 통해서도 페이징을 구현할 수 있습니다. 이 경우 SQL 문법을 그대로 사용할 수 있습니다.
코드 예시
@Query(value = "SELECT * FROM users WHERE name = :name LIMIT :limit OFFSET :offset", nativeQuery = true)
List<User> findUsersByNameWithPaging(@Param("name") String name, @Param("limit") int limit, @Param("offset") int offset);
사용 예
List<User> users = userRepository.findUsersByNameWithPaging("John", 10, 0);

JPQL로 페이징
보드레파지토리
//페이징jpql로
@Query("select b from Board b where b.title like %:title% order by b.id desc")
Page<Board> mFindAll(@Param("title") String title, Pageable pageable);
findAll도 가지고 있다
보드레파지토리 테스트 이동
@Test
public void mFindAll_test(){
//given
String title = "제목";
//when
Pageable pageable = PageRequest.of(0,3);
Page<Board> boardPG = boardRepository.mFindAll(title, pageable);
}
이까지 우리가 짠 것

- 알아서 해준 것
pageable때문에 한 것

페이지 계산해주려고

전체

boardPG 안 내용이 궁금하잖아 이 때는 가장쉬운방법이 json으로 만들어 뿌려보는것!
예가 java을 json으로 json을 java로 바꿔주는 애 @Requestbody할 때 알아서 예가 발동 하나더 있었는데 뭐였지
ObjectMapper om = new ObjectMapper();
@Test
public void mFindAll_test() throws JsonProcessingException {
//given
String title = "제목";
//when
Pageable pageable = PageRequest.of(0,3);
Page<Board> boardPG = boardRepository.mFindAll(title, pageable);
//eye
ObjectMapper om = new ObjectMapper();
//쓰로우 해주기
String responseBody = om.writeValueAsString(boardPG);
//om.readValue(responseBody, (타입 /클래스) Board.class); 이게 뭐하는 거지?
}
이러면 터짐
새로운 쿼리 발동
유저를 조회하는 쿼리 발생

ByteBuddy 오류 잘 기억하자!!!!

왜 터졌을까?
비어있는것을 getter때렸는데 lazy로딩걸리는데 받기 전에 json으로 보내려고 하는 것!!
select하기 전 이기때문 id만 가지고 있고 데이터는 가지고 있지 않아서
→ try catch로 잡을 수 있다 아니면 애초에 dto로 옮겨버린 후 json으로 컨버팅하면 이런 일 없다

친절하게 알려준다
비어있는 객체때문에 실패하는 것을 disable해라!
프로퍼티에 넣어주자(해결)
spring.jackson.serialization.fail-on-empty-beans=false
오류발생
하이버네이트 초기화발생
@JsonIgnore
이거 보드에 reply, user위에 넣으니까 해결됨
→완전한 해결방법 DTO로 바꾸면 된다
package org.example.springv3.board;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.springv3.reply.Reply;
import org.example.springv3.user.User;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.util.List;
@NoArgsConstructor // 빈 생성자 (하이버네이트가 om 할때 필요)
@Setter
@Getter
@Table(name = "board_tb")
@Entity // DB에서 조회하면 자동 매핑이됨
public class Board {
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_incremnt 설정, 시퀀스 설정
@Id // PK 설정
private Integer id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@CreationTimestamp
private Timestamp createdAt;
// fk
//@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
private User user;
// @JsonManagedReference
//@JsonIgnore
//@JsonIgnoreProperties({"board", "createdAt"})
properties는 내부의 json을 무시하는 것
@OneToMany(mappedBy = "board") //게시글 조회할 때 댓글여러개 필요해서 적어준 것이다, 포린키의 주인이 누군지 알려줘야 한다! 나는 아니야(안적으면 컬럼 생성한다)
private List<Reply> replies;
@Builder
public Board(Integer id, String title, String content, Timestamp createdAt, User user) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.user = user;
}
}
눈으로 보기 위해 사용하는 거고 다 테스트 하고 나면은 @JsonIgnore 지우기!!
{"content":[{"id":5,"title":"제목5","content":"내용5","createdAt":1726102168605},{"id":4,"title":"제목4","content":"내용4","createdAt":1726102168605},{"id":3,"title":"제목3","content":"내용3","createdAt":1726102168605}],"pageable":{"pageNumber":0,"pageSize":3,"sort":{"empty":true,"unsorted":true,"sorted":false},"offset":0,"unpaged":false,"paged":true},"totalElements":5,"totalPages":2,"last":false,"size":3,"number":0,"sort":{"empty":true,"unsorted":true,"sorted":false},"first":true,"numberOfElements":3,"empty":false}
이 값을 여기 사이트 text에넣고 view를 눌러보자 이쁘게 나온다!

number이 현제 페이지
번호 붙이고 싶으면 totalPages만큼 for문돌리면 된다!!
페이지에 보드 타입이라 .getContent하면 안의 정보 다 있다 이제는 List가 아니니까
투 스트링을 유저, 보드, 리플라이에도 넣는다
보드 객체 꺼내서 getContent해서 syout하면 어떻게 될까? 터진다!
왜 터질까?
stachOverFlower로 메모리 터짐
계속 타고 타고 타고 들어가서 터진다! toString으로 무한 참조해서 터진다
테스트 코드
@Test
public void mFindAllV2_test() throws JsonProcessingException {
// given
String title = "제목";
// when
Pageable pageable = PageRequest.of(0, 3);
Page<Board> boardPG = boardRepository.mFindAll(title, pageable);
// eye
System.out.println(boardPG.getContent());
}

해결하려면
→ reply에서 보드 넣는것 toString만 지우면 끝난다!

@DATA안에 getter, setter, toString 등 여러가지가 자동호출돼서 초보자 때는 잘 쓰지않는다 제어할 수 없으니
리팩토링 보드서비스 게시글 목록보기
원래 코드
public List<Board> 게시글목록보기(String title) {
//전체 결과
if(title == null){
//Pageable pg = PageRequest.of(0, 3, Sort.Direction.DESC, "id");
// 게시글 순서 거꾸로 만드려고
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<Board> boardList = boardRepository.findAll(sort);
return boardList;
// 검색된 결과
}else {
List<Board> boardList = boardRepository.mFindAll(title);
return boardList;
}
}
변한 것
public Page<Board> 게시글목록보기(String title, int page) {
//전체 결과
Pageable pageable = PageRequest.of(page*3, 3, Sort.Direction.DESC, "id");
if(title == null){
// 게시글 순서 거꾸로 만드려고
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Page<Board> boardList = boardRepository.findAll(pageable);
return boardList;
// 검색된 결과
}else {
Page<Board> boardList = boardRepository.mFindAll(title, pageable);
return boardList;
}
}
DTO로 해주는게 좋다 하지만 여기서는 직접 머스테치에 선별에서 줄꺼니까 안터짐
보드 컨트롤러 이동
//localhost:8080?title=제목 이러면? requestParam 생략 가능
// 이유는 적어주면 requestParam에서(defaultValue ="", name="title") 이렇게 쓸 수 있다 쿼리 안에 title이 없으면 터지는데 (파싱 못하니까) 그래서 공백으로 넣어라 할 수 있다.!
@GetMapping("/")
public String list(@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page,
HttpServletRequest request) {
//쿼리스트링으로 page넣을 거임
// 디폴트 값 넣을 수 있다.
/*
if(page == null) {
page = 0;
}
이렇게 넘길 수 있다. /호출하면 넣어줄 수 있다.
defaultValue는 쿼리스트링으로 받는 거라서 숫자 0으로는 안됨 문자열 0으로 해야함 그래서 defaultValue하면 if 위에거 사용 안해도 됨
*/
Page<Board> boardPG = boardService.게시글목록보기(title,page);
//가방에 담고 list에 있는 것만 꺼낼꺼니까 DTO안만들어도 됨
request.setAttribute("model", boardList);
return "board/list";
}
이제 객체니까 models가 아닌 model로 바꿔야 한다
boardList를 boardPG로 명확하게 바꿔주자
list 이동
왜 model.content인가 content안에 내용들 다 들어가 있으니까 아까 봤잖아 json으로
원래는 json으로 값 넣으면 너무 많은 정보들이 있어서 다 넣으니까 터져서 DTO를 만들어야하는데
머스테치에서는 내가 원하는 값만 넣을거여서 터지지 않는다!
모델.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>
위치

결과

게시글 목록보기
*3 없애기
list로
disable지워야 한다 연습하려면
<h1>{{model.number}}</h1>
<ul class="pagination d-flex justify-content-center">
<li class="page-item "><a class="page-link" href="?page=0">Previous</a></li>
<li class="page-item"><a class="page-link" href="?page={{model.number+1}}">Next</a></li>
</ul>
model.number하면 현 번호가 있는데
+1 -1 이렇게 하면 안됨! 머스테치에서는 연산이 불가해서
보드 컨트롤러
@GetMapping("/")
public String list(@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page,
HttpServletRequest request) {
//쿼리스트링으로 page넣을 거임
// 디폴트 값 넣을 수 있다.
/*
if(page == null) {
page = 0;
}
이렇게 넘길 수 있다. /호출하면 넣어줄 수 있다.
defaultValue는 쿼리스트링으로 받는 거라서 숫자 0으로는 안됨 문자열 0으로 해야함 그래서 defaultValue하면 if 위에거 사용 안해도 됨
*/
Page<Board> boardPG = boardService.게시글목록보기(title,page);
//가방에 담고 list에 있는 것만 꺼낼꺼니까 DTO안만들어도 됨
request.setAttribute("model", boardPG);
request.setAttribute("prev", boardPG.getNumber()-1);
request.setAttribute("next", boardPG.getNumber()+1);
return "board/list";
}
그래서 컨트롤러에서 넣어주면 되기는 한다
하지만 서비스에서 DTO를 만들어서 해줘야 한다
list
<h1>{{model.number}}</h1>
<ul class="pagination d-flex justify-content-center">
<li class="page-item "><a class="page-link" href="?page={{prev}}">Previous</a></li>
<li class="page-item"><a class="page-link" href="?page={{next}}">Next</a></li>
</ul>
숙제
검색은 일단 버리고
보드컨트롤러의
request.setAttribute("model", boardPG);
request.setAttribute("prev", boardPG.getNumber()-1);
request.setAttribute("next", boardPG.getNumber()+1);
게시글목록보기에서 한번에 만들어라
하나의 객체로 DTO로 만들어서 한번에 응답 받아라
즉 게시글 목록보기DTO만들어라
이런게 뭔데? 많은 데이터들이 있는데 머스테치에서 뽑아써서?
이런거는 서버사이드랜더링에서는 동작하지만
resAPI로 넘어가면 문제가 된다!
숙제 내가 만들어본 것 DTO만들기
BoardResponse
package org.example.springv3.board;
import lombok.Data;
import org.example.springv3.reply.Reply;
import org.example.springv3.user.User;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.List;
public class BoardResponse {
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isOwner;
// private Integer userId;
private String username;
//리플라이 엔티티 집이 넣으면 안된다 레이지 로딩 되니까! 똑같이 생긴 DTO만들면 된다 비영속객체 만들어서 응답하게 하는게 좋다!
private List<ReplyDTO> replies = new ArrayList<>();
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.isOwner = false;
if (sessionUser != null) {
if (board.getUser().getId() == sessionUser.getId()) {
isOwner = true; // 권한체크
}
}
// this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
for(Reply reply : board.getReplies()) {
replies.add(new ReplyDTO(reply,sessionUser));
}
}
}
@Data
public static class ReplyDTO{
private Integer id;
private String comment;
private String username;
private Boolean isOwner;
public ReplyDTO(Reply reply, User sessionUser) {
this.id = reply.getId();
this.comment = reply.getComment();
this.username = reply.getUser().getUsername();
this.isOwner = false;
if (sessionUser != null) {
if (reply.getUser().getId() == sessionUser.getId()) {
isOwner = true; // 권한체크
}
}
}
}
@Data
public static class DTO {
private Integer id;
private String title;
private String content;
public DTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
}
}
@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> boardPage) {
this.number = boardPage.getNumber();
this.totalPage = boardPage.getTotalPages();
this.size = boardPage.getSize();
this.first = boardPage.isFirst();
this.last = boardPage.isLast();
this.prev = boardPage.getNumber()-1;
this.next = boardPage.getNumber()+1;
//for로 id title만 있으면 확인이 가능하다
//어디서 값을 가지고 와야 하지?
for(Board board : boardPage.getContent()) {
contents.add(new Content(board));
}
}
//생성자를 만들어서
@Data
class Content {
private Integer id;
private String title;
public Content(Board board) {
this.id = board.getId();
this.title = board.getTitle();
}
}
}
}
boardService
public BoardResponse.PageDTO 게시글목록보기(String title, int page) {
//전체 결과
Pageable pageable = PageRequest.of(page, 3, Sort.Direction.DESC, "id");
Page<Board> boardList;
if (title == null) {
// 게시글 순서 거꾸로 만드려고
Sort sort = Sort.by(Sort.Direction.DESC, "id");
boardList = boardRepository.findAll(pageable);
// 검색된 결과
} else {
boardList = boardRepository.mFindAll(title, pageable);
}
return new BoardResponse.PageDTO(boardList);
}
BoardController
@GetMapping("/")
public String list(@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page,
HttpServletRequest request) {
BoardResponse.PageDTO boardPG = boardService.게시글목록보기(title,page);
request.setAttribute("model", boardPG);
return "board/list";
}
List.mustache
{{>layout/header}}
<form action="/text/form" method="post">
<input type="text" name="username">
<button>고고</button>
</form>
<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}}
<ul class="pagination d-flex justify-content-center">
<li class="page-item "><a class="page-link" href="?page={{model.prev}}">Previous</a></li>
<li class="page-item"><a class="page-link" href="?page={{model.next}}">Next</a></li>
</ul>
</div>
{{>layout/footer}}
강사님 페이징
BoardResponse
package org.example.springv3.board;
import lombok.Data;
import org.example.springv3.reply.Reply;
import org.example.springv3.user.User;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.List;
public class BoardResponse {
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Boolean isOwner;
// private Integer userId;
private String username;
//리플라이 엔티티 집이 넣으면 안된다 레이지 로딩 되니까! 똑같이 생긴 DTO만들면 된다 비영속객체 만들어서 응답하게 하는게 좋다!
private List<ReplyDTO> replies = new ArrayList<>();
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.isOwner = false;
if (sessionUser != null) {
if (board.getUser().getId() == sessionUser.getId()) {
isOwner = true; // 권한체크
}
}
// this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
for(Reply reply : board.getReplies()) {
replies.add(new ReplyDTO(reply,sessionUser));
}
}
}
@Data
public static class ReplyDTO{
private Integer id;
private String comment;
private String username;
private Boolean isOwner;
public ReplyDTO(Reply reply, User sessionUser) {
this.id = reply.getId();
this.comment = reply.getComment();
this.username = reply.getUser().getUsername();
this.isOwner = false;
if (sessionUser != null) {
if (reply.getUser().getId() == sessionUser.getId()) {
isOwner = true; // 권한체크
}
}
}
}
@Data
public static class DTO {
private Integer id;
private String title;
private String content;
public DTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
}
}
@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<>();
private List<Integer> numbers = new ArrayList<>();
public PageDTO(Page<Board> boardPage) {
this.number = boardPage.getNumber();
this.totalPage = boardPage.getTotalPages();
this.size = boardPage.getSize();
this.first = boardPage.isFirst();
this.last = boardPage.isLast();
this.prev = boardPage.getNumber()-1;
this.next = boardPage.getNumber()+1;
int temp = (number / 3)*3; // 0-> 0, 3->3, 6->6
for(int i = temp; i<temp+2; i++){
this.numbers.add(i);
}
//for로 id title만 있으면 확인이 가능하다
//어디서 값을 가지고 와야 하지?
for(Board board : boardPage.getContent()) {
contents.add(new Content(board));
}
}
//생성자를 만들어서
@Data
class Content {
private Integer id;
private String title;
public Content(Board board) {
this.id = board.getId();
this.title = board.getTitle();
}
}
}
}
List.mustache
{{#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}}
<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>
변경 사항은 이 두개 말고는 없다
Share article