[본 포스팅은 실전! 스프링 데이터 JPA -인프런 편을 기반으로 작성하였습니다.]
어떤 게시판을 봐도 모든 게시글을 한 페이지에 넣어 보여주는 서비스는 이 세상에 절대 없을 것이다.
그리고 그 게시글들을 페이지 별로 나누는 것이 페이징이다.
스프링 데이터 JPA에서는 페이징 기능을 편리하게 제공한다.
1. JPA Repository에서 반환타입(Page)과 파라미터(Pageble)를 넣어준다.
예를 들어 나이가 10살인 멤버 목록을 가져오는데 이때 페이징 기능을 쓰고 싶다고 가정을 한다.
그러면 JPA Repository(인터페이스)에서 아래와 같이 추가를 해주면 된다.
Page<Member> findByAge(int age, Pageable pageable);
반환 타입이 Page여야 하고 파라미터에 Pageable을 넣어주면 일단 페이징을 간단히 할 수 있도록 도와준다.
2. Page<Member>를 받아서 페이징 사용하기.
먼저 전체 코드(테스트)부터 보자.
@Test
public void paging() {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 10));
memberRepository.save(new Member("member3", 10));
memberRepository.save(new Member("member4", 10));
memberRepository.save(new Member("member5", 10));
int age = 10;
//0번째 페이지를 보여줘! 그리고 3개씩 나눠서 보여줘! 정렬은 username으로 내림차순!
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
//when
Page<Member> page = memberRepository.findByAge(age, pageRequest);
//Member를 DTO로 변환
Page<MemberDto> toMap = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));
//then
List<MemberDto> content = toMap.getContent();
assertThat(content.size()).isEqualTo(3); //컨텐츠 사이즈
assertThat(toMap.getTotalElements()).isEqualTo(5); //멤버 개수
assertThat(toMap.getNumber()).isEqualTo(0); //페이지 번호
assertThat(toMap.getTotalPages()).isEqualTo(2); //전체 페이지 개수
assertThat(toMap.isFirst()).isTrue(); //페이지가 첫 번째 페이지인가
assertThat(toMap.hasNext()).isTrue(); //다음 페이지가 있는지
}
위 코드는 테스트 케이스이며 페이징을 통해 나이가 10살인 멤버를 3개씩 나눈 것을 0번째 페이지를 표현한 것이다.
실행해보면 username을 내림차순 조건을 주었기 때문에 다음과 같이 가져온 것을 볼 수 있다.
코드를 위에부터 설명해 보자면
given
멤버 5명을 DB에 넣고 findByAge니까 나이 10을 줄 것이라고 미리 변수에 넣어뒀다.
그리고 페이징 요청을 위해 PageRequest를 사용하는데
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
PageRequest.of(페이지 번호, 개수, 정렬 조건)이다.
즉, 나중에 페이징을 통해 게시판을 만든다고 하면 사용자가 다음 버튼을 눌렀을 때
현재 페이지 번호 +1을 해준 값을 첫 번째 파라미터로 넣어주면 된다.
개수는 개발자 마음대로~
그리고 정렬 조건은 오름차순(ASC), 내림차순(DESC)을 주고 어떤 것을 기준으로 줄 것인지 String 형으로 주면 된다.
when
when에서 첫 번째 코드는 findByAge를 호출하여 페이징 처리된 멤버를 가져온다.
그리고 Member를 그냥 내보내지 않고 MemberDto로 만들어 내보내는 과정이다.
(Dto로 내보내야 하는 이유는 마지막에 서술)
map을 사용하여 한 번에 DTO로 바꾸는 모습!
Page<MemberDto> toMap = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));
then
요청한 페이징의 알맹이(멤버)만 빼내는 게 getContent()다.
그 밑으로는 아래의 정보를 알 수 있다.
- content.size(): 콘텐츠 사이즈
- getTotalElements(): 멤버 개수(가져온 데이터 총 개수)
- getNumber(): 페이지 번호(0부터 시작함)
- getTotalPages(): 전체 페이지 수
- isFirst(): 페이지가 첫 번째 페이지인지
- hasNext(): 다음 페이지가 있는지
스프링 MVC로 게시글 띄워보기
※상황) 게시글 리스트를 페이징 해서 띄워준다.
1. 컨트롤러에서 아래의 파라미터를 넣어준다.
@PageableDefault(size = 10, sort = "id", direction = Sort.Direction.DESC) Pageable pageable
size: 10개씩
sort: id를 기준으로
direction: DESC 정렬한다.
2. 서비스로 게시글 리스트를 가져올 때 pageable을 같이 보낸다.
Page<PostDto> posts = postService.findByTitleLikePageList(form.getTitle(), pageable);
위와 같이 서비스 부에 요청을 보낸다.
서비스 부 코드)
public Page<PostDto> findByTitleLikePageList(String title, Pageable pageable) {
return postRepository.findByTitleContains(title, pageable).map(post -> new PostDto(post.getId(), post.getSentence(), post.getTitle(), post.getDate(), post.getMemberIdFk()));
}
3. JPA 인터페이스
Page<Post> findByTitleContains(String title, Pageable pageable);
일단 여기까지 거치게 되면 게시물은 dto에 다 담기게 된다.
4. 컨트롤러에서 model.addAttribute로 정보 넣어주기
int nowPage = posts.getPageable().getPageNumber() + 1;
model.addAttribute("posts", posts);
model.addAttribute("previous", pageable.previousOrFirst().getPageNumber());
model.addAttribute("next", pageable.next().getPageNumber());
model.addAttribute("nowPage", nowPage);
model.addAttribute("startPage", Math.max(nowPage - 4, 1));
model.addAttribute("endPage", Math.min(nowPage + 5, posts.getTotalPages()));
포스트들을 다 넣어주고
이전 버튼, 다음 버튼, 현재 페이지, 시작 페이지, 끝 페이지 정보를 넣어주고 뷰에서 받아 데이터를 표시해주면 된다.
DTO를 사용해야 하는 이유
대부분의 로직에서 Service 부에서 Repository에 데이터를 요청해서 받게 된다.
근데 이렇게 받은 데이터를 컨트롤러로 바로 보내면 절대 안 된다.
그 이유는 엔티티는 외부로 노출이 되면 안 되기 때문이다.
만약 엔티티가 컨트롤러까지 살아서 만약 데이터 변경이 일어나면 더티체킹으로 의도치 않게 데이터가 변경될 수도 있고,
영속성 콘텍스트가 컨트롤러까지 살아있으면 안 된다.
왜냐하면 커넥션 풀의 연결은 짧게 짧게 진행이 되어야 하는데 컨트롤러까지 가서 요청이 길어지면 그 뒤에 오는 트래픽들이 많아질 경우 데드락이 생길 수 있다.
따라서 서비스 부에서 DTO를 사용하여 컨트롤러로 넘겨주자!
'JAVA > JPA' 카테고리의 다른 글
[JPA] Auditing을 사용해 등록일, 수정일, 등록자, 수정자 기록 남기기 (0) | 2023.01.03 |
---|---|
[JPA] 스프링 데이터 JPA로 벌크성 수정 쿼리 날려보기 (0) | 2023.01.02 |
[JPA/JPQL] 페치 조인(fetch join)이란? 특징과 한계 - 엔티티 페치 조인, 컬렉션 페치 조인, DISTINCT로 중복 제거 (0) | 2022.12.29 |
[JPA] @MappedSuperclass로 공통 칼럼 상속받기 (0) | 2022.12.23 |
[JPA] 상속관계 전략과 매핑 (코드 예시) (0) | 2022.12.23 |