카테고리 없음

[Spring][Spring data JPA] Spring data JPA의 페이지

키보드발 2022. 10. 10. 22:24
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()));

 

 

https://inf.run/6vnU

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com