[Spring][Spring data JPA] Spring data JPA의 페이지
public interface MemberRepository extends JpaRepository<Member, Long> {
Page<Member> findByAge(int age, Pageable pageable);
}
✔ Pageable, import 잘해야함
✔ JPA의 페이지 기능을 써주기 위해서는 Page제네릭안에 페이징하고 싶은 엔티티를 넣어줘야함
✔ int age는 where과 같은 역할임
✔ pageable은 첫 번째 페이지, 페이지당 보여줄 데이터는 3건(단 이름으로 내림차순으로 정렬해라)와 같은 옵션을 줄 수 있음
import org.springframework.data.domain.Pageable;
다른 것으로 import하면 오류남
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Autowired
TeamRepository teamRepository;
@Test
public void paging() {
//given
memberRepository.save(new Member("member3", 10));
memberRepository.save(new Member("member2", 10));
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member4", 10));
memberRepository.save(new Member("member5", 10));
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
//when
Page<Member> page = memberRepository.findByAge(age, pageRequest);
//then
List<Member> content = page.getContent();
long totalElements = page.getTotalElements();
for (Member member : content) {
System.out.println("member = " + member);
}
System.out.println("totalElements = " + totalElements);
}
}
기준
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
나이가 10살인 사람을 첫 번째 페이지, 페이지당 보여줄 데이터는 3건(단 이름으로 내림차순으로 정렬해라)
Page<Member> findByAge(int age, Pageable pageable);를 사용하면 해당하는 레코드뿐만 아니라
해당하는 totalCount도 같이 구할 수 있음
List<Member> content = page.getContent();
long totalElements = page.getTotalElements();
for (Member member : content) {
System.out.println("member = " + member);
}
//결과값
member = Member(id=5, username=member5, age=10)
member = Member(id=4, username=member4, age=10)
member = Member(id=1, username=member3, age=10)
totalElements = 5
해당 코드를 사용함으로서 query가 두개 보내지는 것을 알 수 있다.
Page<Member> page = memberRepository.findByAge(age, pageRequest);
//Page<Member> page = memberRepository.findByAge(age, pageRequest);
//age=10일때 첫 번째 페이지, 페이지당 보여줄 데이터는 3건
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.age=?
order by
member0_.username desc limit ?
//select member0_.member_id as member_i1_0_, member0_.age as age2_0_, member0_.team_id as team_id4_0_, member0_.username as username3_0_ from member member0_ where member0_.age=10 order by member0_.username desc limit 3;
//age=10일때 해당하는 totalCount값
select
count(member0_.member_id) as col_0_0_
from
member member0_
where
member0_.age=?
//select count(member0_.member_id) as col_0_0_ from member member0_ where member0_.age=10;
page는 slice를 상속받음
slice에는 totalcount를 가져오지 않음
org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는 페이징
org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능
(내부적으로 limit + 1조회)
List (자바 컬렉션): 추가 count 쿼리 없이 결과만 반환
정리하자면 Page와 Slice는 결국 페이지를 가져올때 쓰는 반환타입임
그렇다면 어디서 차이가 있느냐
Page를 반환타입으로 하면 totalCount쿼리가 보냄으로써 해당하는 데이터의 전체 개수를 알 수 있고
->해당하는 데이터의 전체 개수를 알 수 있음으로-> 해당하는 데이터가 전체 몇 페이지가 될지도 알 수 있음
Slice를 반환타입으로하면 totalCount쿼리가 안 보내지고 Limit+1만 더 조회함으로서 다음 페이자가 있느냐 없느냐만 알 수 있음 따라서 해당하는 데이터의 전체개수를 알 수 없고 -> 해당하는 데이터가 전체 몇페이지가 될지도 알 수 없음
List를 써서 Page와 slice 기능 없이 단순히 결과만 가져올 수 도 있음
해당코드를 써서 page나 slice 없이 0번째부터 3개 이런식으로 가져올 수 도 있음
List<Member> findByAge(int age, Pageable pageable);
Page를 쓰면 TotalCount를 조회하기위해 추가 쿼리가 보내짐
하지만 그때 필요없는 리소스를 소모할 수 있음, 왜냐하면
select count(username) from Member left join Team On Member.TeamId = Team.Id와 같이 CountQuery에 쓸데없는 join으로 인해 리소스를 먹을 수 있기때문임
따라서 countQuery를 따로 지정해 줄 수 있음
✔ 실제로 필요없는 리소스를 먹는 예(countQuery를 지정해주지 않으니 value를 토대로 자동으로 TotalCount를 위해 쿼리를 만들어서 보내주는데 join을 해버려서 쓸데없는 리소스를 사용하고 있음)
@Query(value = "select m f rom Member m left join m.team t")
Page<Member> findByAge(int age, Pageable pageable);
데이터를 조회하기 위한 쿼리가 보내짐
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
left outer join
team team1_
on member0_.team_id=team1_.team_id
order by
totalcount를 위한 쿼리가 보내짐
select
count(member0_.member_id) as col_0_0_
from
member member0_
left outer join
team team1_
one member0_.team_id=team1_.teamid
데이터를 가져오는 쿼리에서만 join이 나가되는 경우이고 (멤버와 팀의 정보가 필요하기 때문이다.)
count에서는 join이 필요하지않음(왜냐하면 어처피 멤버와 팀은 다대일관계로서 서로 무조건 연관되어있음,
즉 멤버 lefjoin 팀의 개수는 그냥 멤버 개수랑 동일함, 단지 팀의 정보를 가져오기위해서 조인할뿐임, 따라서 count에서 까지 join할 필요가 없음)
@Query(value = “select m from Member m”,
countQuery = “select count(m.username) from Member m”)
Page<Member> findMemberAllCountBy(Pageable pageable);
✔ sort조건이 복잡하면 굳이 쓸데없이 안빼도 됨
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
//when
Page<Member> page = memberRepository.findByAge(age, pageRequest);
public interface MemberRepository extends JpaRepository<Member, Long> {
Page<Member> findByAge(int age, Pageable pageable);
}
아래와 같이 @Query에 그냥 다 넣어도 된다는 것임
위와 같이 sort를 따로두면 경우에 따라 desc asc선택할 수 있게 할 수 있다는게 장점일듯 싶다.
@Query(value = "select m from Member m where m.age =:age ORDER BY m.username desc",
countQuery = "select count(m.username) from Member m")
Page<Member> findAge(@Param("age") int age,Pageable pageable);
int age = 10;
PageRequest pageRequest = PageRequest.of(0, 3);
Page<Member> page = memberRepository.findAge(age,pageRequest);
member = Member(id=5, username=member5, age=10)
member = Member(id=4, username=member4, age=10)
member = Member(id=1, username=member3, age=10)
그냥 query문에 싹다 넣어버리고 page조건만 넣어도 똑같은 결과가 나옴
✔ 여기서 한가지 알아낸것은
스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행 만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.
->@Query가 먼저라는 거임
✔ API로 페이지를 반환할때 마찬가지로 엔티티로 보내면 안됨 따라서 DTO로 변환해서 보내줘야함
그 방법이 이거임
->여기서 걱정할 수 있음. json으로 보낼때 그러면 page에서 제공해주는 전체 데이터수나 페이지수, Pageable로 해당되는데이터는 어떻게 되는거냐고. 알아서 다 잘 변환되어 데이터에 담겨 보내짐
Page<Member> page = memberRepository.findByAge(age, pageRequest);
Page<MemberDto> toMap = page.map(member -> new MemberDto(member.getId(), member.getUsername(), member.getTeam().getName()));
실전! 스프링 데이터 JPA - 인프런 | 강의
스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.
www.inflearn.com