- 메소드 이름으로 쿼리 생성
- NameQuery
- @Query - 리포지토리 메소드에 쿼리 정의
- Parameter binding
- Return type
- 페이징과 정렬
- 벌크성 수정 쿼리
- @EntityGraph
쿼리 메소드에 대해서 3단계의 시리즈 중 3번째 시리즈입니다.
이번 글에서는 메소드 이름으로 페이징과 정렬, 벌크성 수정 쿼리, @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();
"본 포스트는 작성자가 공부한 내용을 바탕으로 작성한 글입니다.
잘못된 내용이 있을 시 언제든 댓글로 피드백 부탁드리겠습니다.
항상 정확한 내용을 포스팅하도록 노력하겠습니다."
'Spring' 카테고리의 다른 글
Spring data JPA: save() 메소드 (0) | 2023.02.06 |
---|---|
Spring data JPA: 확장 기능 (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (2) (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (1) (0) | 2023.02.06 |
Spring data JPA: 공통 인터페이스 기능 (0) | 2023.02.06 |
- 메소드 이름으로 쿼리 생성
- NameQuery
- @Query - 리포지토리 메소드에 쿼리 정의
- Parameter binding
- Return type
- 페이징과 정렬
- 벌크성 수정 쿼리
- @EntityGraph
쿼리 메소드에 대해서 3단계의 시리즈 중 3번째 시리즈입니다.
이번 글에서는 메소드 이름으로 페이징과 정렬, 벌크성 수정 쿼리, @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();
"본 포스트는 작성자가 공부한 내용을 바탕으로 작성한 글입니다.
잘못된 내용이 있을 시 언제든 댓글로 피드백 부탁드리겠습니다.
항상 정확한 내용을 포스팅하도록 노력하겠습니다."
'Spring' 카테고리의 다른 글
Spring data JPA: save() 메소드 (0) | 2023.02.06 |
---|---|
Spring data JPA: 확장 기능 (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (2) (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (1) (0) | 2023.02.06 |
Spring data JPA: 공통 인터페이스 기능 (0) | 2023.02.06 |