쿼리 메소드 (3)

dongbin_Shin·2021년 7월 29일
2

스프링 데이터 JPA

목록 보기
4/6
post-thumbnail

  1. 메소드 이름으로 쿼리 생성
  2. NameQuery
  3. @Query - 리포지토리 메소드에 쿼리 정의
  4. 파라미터 바인딩
  5. 반환 타입
  6. 페이징과 정렬
  7. 벌크성 수정 쿼리
  8. @EntityGraph

6. 페이징과 정렬

페이징과 정렬 파라미터

  • org.springframework.data.domain.Sort : 정렬 기능
  • org.springframework.data.domain.Slice : 페이징 기능 (내부에 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 쿼리 사용 안함
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByUsername(String name, Sort sort);

페이징과 정렬 사용 예제 코드

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

정의 코드

public interface MemberRepository extends Repository<Member, Long> {
	Page<Member> findByAge(int age, Pageable pageable);
}

실행 코드

@Test
public void page() throws Exception{

    for(int i = 0; i < 5; i++){ 
    	memberRepository.save(new Member("member" + i, 10));
    }
    
    PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
    Page<Member> page = memberRepository.findByAge(10, pageRequest);
    
    List<Member> content = page.getContent(); // 조회된 데이터
    assertEquals(3, content.size()); // 조회된 데이터 수
    assertEquals(5, totalElements); // 전체 데이터 수
    assertEquals(2, page.getTotalPages()); //전체 페이지 번호
    assertEquals(0, page.getNumber()); // 페이지 번호
    assertTrue(page.isFirst()); // 첫번째 항목인가?
    assertTrue(page.hasNext()); // 다음 항목이 있는가?
    assertFalse(page.hasPrevious()); // 이전 항목이 있는가?
}

  • Pageable은 인터페이스다. 따라서 실제 사용할 때는 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 객체를 사용한다.
  • PageRequest(현재 페이지, 조회할 데이터 수, (정렬 정보))
  • 페이지는 0부터 시작

Page 인터페이스

public interface Page<T> extends Slice<T> {
	int getTotalPages(); //전체 페이지 수
	long getTotalElements(); //전체 데이터 수
	<U> Page<U> map(Function<? super T, ? extends U> converter); //변환기
}

Slice 인터페이스

조회시 limit+1 조회 -> 다음 페이지 여부 확인 (최근 모바일 리스트의 더보기)

public interface Slice<T> extends Streamable<T> {
	int getNumber();	//현재 페이지
    int getSize();	        //페이지 크기
    int getNumberOfElements();	//현재 페이지에 나올 데이터 수
    List<T> getContent();	//조회된 데이터
    boolean hasContent();	//조회된 데이터 존재 여부
    Sort getSort();	        //정렬 정보
    boolean isFirst();          //현재 페이지가 첫 페이지인지 여부
    boolean isLast();	        //현재 페이지가 마지막 페이지인지 여부
    boolean hasNext();          //다음 페이지 여부
    boolean hasPrevious();      //이전 페이지 여부
    Pageable getPageable();     //페이지 요청 정보
    Pageable nextPageable();    //다음 페이지 객체
    Pageable previousPageable();//이전 페이지 객체
    <U> Slice<U> map(Function<? super T, ? extends U> converter); //변환기
}

count 쿼리를 분리할 수 있음

카운트 쿼리는 매우 무겁기 때문에 필요하지 않은 left join 을 제거하는 것이 실무에서 중요하다!

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

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

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

7. 벌크성 수정 쿼리

@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
  • 벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용
    • 사용하지 않으면 다음 예외 발생
    • org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations
  • 벌크성 쿼리 실행 후 영속성 컨텍스트 초기화: @Modifying(clearAutomatically = true)
    • 이 옵션 없이 회원을 조회하면 영속성 컨텍스트에 과거 값이 남아서 문제가 발생할 수 있다.

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 접근해 데이터를 변경하기 때문에 변경 전 데이터 값이 영속성 컨텍스트에 남아 있게 된다.
즉, DB 값과 영속성 컨텍스트 값이 불일치하게 된다.
<권장하는 방안>
1. 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산 수행
2. 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트를 초기화

8. @EntityGraph

연관된 엔티티를 한번에 조회하는 방법

지연로딩 관계를 갖는 필드를 조회할 때 마다 쿼리가 실행된다. (N + 1 문제 발생)

//JPQL 패치 조인
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();

//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

//메서드 이름 쿼리에서 특히 편리하다.
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
  • FETCH JOIN의 간편 버전
  • LEFT OUTER JOIN 사용
@NamedEntityGraph(name = "Member.all", attributeNodes =
	@NamedAttributeNode("team"))
@Entity
public class Member {
...
}
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

"본 포스트는 작성자가 공부한 내용을 바탕으로 작성한 글입니다.
잘못된 내용이 있을 시 언제든 댓글로 피드백 부탁드리겠습니다.
항상 정확한 내용을 포스팅하도록 노력하겠습니다."

profile
멋있는 백엔드 개발자

0개의 댓글