3 분 소요


목차

  • 파라미터 바인딩
  • 반환 타입
  • 페이징과 정렬


파라미터 바인딩

  • 위치 기반
  • 이름 기반
select m from Member m where m.username = ?0 // 위치 기반
select m from Member m where m.username = :name // 이름 기반

파라미터 바인딩

public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query("select m from Member m where m.username = :name")
    Member findMembers(@Param("name") String username);
}

[참고]
코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용!(위치 기반은 순서가 바뀔수도 있다..)

컬렉션 파라미터 바인딩
Collection 타입으로 in절 지원

@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);


반환 타입

스프링 데이터 JPA는 유연한 반환 타입 지원

List<Member> findByUsername(String name); // 컬렉션
Member findByUsername(String name); // 단건
Optional<Member> findByUsername(String name); // 단건 Optional

[참고] 스프링 데이터 JPA 공식 문서

조회 결과가 많거나 없으면?

  • 컬렉션
    • 결과 없음 : 빈 컬렉션 반환
  • 단건 조회
    • 결과 없음 : null 반환
    • 결과가 2건 이상 : javax.persistence.NonUniqueResultException 예외 발생

[참고]
단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다. 이 메서드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException 예외가 발생하는데 개발자 입장에서는 다루기가 불편하기 때문에, 스프링 데이터 JPA는 단건 조회할 때 이 예외가 발생하면 예외를 무시하고 null을 반환해준다.


페이징과 정렬

  • 검색 조건 : 나이가 10살
  • 정렬 조건 : 이름으로 내림차순
  • 페이징 조건 : 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

순수 JPA

MemberJpaRepository

public List<Member> findByPage(int age, int offset, int limit) {
    return em.createQuery(
            "select m from Member m where m.age = :age order by m.usrname desc")
            .setParameter("age", age)
            .setFirstResult(offset)
            .setMaxResults(limit)
            .getResultList();
}

public long totalCount(int age) {
    return em.createQuery(
            "select count(m) from Member m where m.age = :age", Long.class)
            .setParameter("age", age)
            .getSingleResult();
}
  • 테스트 코드에서 age = 10, offset = 0, limit = 3을 넘겨준다.

스프링 데이터 JPA

페이징과 정렬 파라미터

  • org.springframework.data.domain.Sort : 정렬 기능
  • org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort 포함)

특별한 반환 타입

  • org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는 페이징
  • org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능
    • 내부적으로 limit + 1 조회
  • List(자바 컬렉션) : 추가 count 쿼리 없이 결과만 반환

페이징과 정렬 사용 예

Page<Member> findByUsername(String name, Pageable pageable); // count 쿼리 사용
Slice<Member> findByUsername(String name, Pageable pageable); // count 쿼리 사용 X
List<Member> findByUsername(String name, Pageable pageable); // count 쿼리 사용 X
List<Member> findByUsername(String name, Sort sort);

Page 사용 예제 정의 코드

public interface MemberRepository extends JpaRepository<Member, Long> {

    Page<Member> findByAge(int age, Pageable pageable);
}

Page 사용 예제 실행 코드

@Test
void page() {
    // 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));

    //when
    PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
    Page<Member> page = memberRepository.findByAge(10, pageRequest);

    //then
    List<Member> content = page.getContent(); //조회된 데이터

    assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
    assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수
    assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
    assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호
    assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
    assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
}
  • 두 번째 파라미터로 받은 Pageable은 인터페이스다. 따라서, 실제 사용할 때는 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 객체를 사용한다.
  • PageRequest 생성자의 첫 번째 파라미터에는 현재 페이지를, 두 번째 파라미터에는 조회할 데이터 수를 입력한다. 여기에 추가로 정렬 정보도 파라미터로 사용할 수 있다.

[주의] : Page는 1부터가 아닌 0부터 시작이다.

Slice는 count 쿼리가 나가지 않는다.(스크롤을 끝까지 내리면 더보기가 있는 것과 같다.) 더 보기와 같은 기능 때문에 limit에 1을 더해서 값이 다음에 더 있는지 체크한다.

count 쿼리 분리

@Query(value = "select m from Member m",
        countQuery = "select count(m.username) from Member m")
Page<Member> findMemberAllCountBy(Pageable pageable);

페이지를 유지하면서 엔티티를 DTO로 변환하기

Page<Member> page = memberRepository.findByAge(10, pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto());

실습

  • Page
  • Slice(count X) 추가로 limit + 1을 조회한다. 그래서 다음 페이지 여부 확인(Ex. 모바일 리스트)
  • List(count X)
  • 카운트 쿼리 분리(복잡한 sql에서 사용, 데이터는 left join, 카운트는 left join 안해도 된다.)
    • 실무에서 매우 중요!!


<출처 : 인프런 - 실전! 스프링 데이터 JPA(김영한)>

댓글남기기