✔️ 페이징과 정렬
페이징과 정렬 파라미터
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 인터페이스
public interface Page<T> extends Slice<T> {
int getTotalPages(); //전체 페이지 수
long getTotalElements(); //전체 데이터 수
<U> Page<U> map(Function<? super T, ? extends U> converter); //mapper
}
Slice 인터페이스
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); //mapper
}
사용 예
Page<Member> findByUsername(String name, Pageable pageable) // count 쿼리 사용
Slice<Member> ...(String name, Pageable pageable) // count 쿼리 사용 X
List<Member> ...(String name, Pageable pageable) // count 쿼리 사용 X
List<Member> ...(String name, Sort sort)
count 쿼리 분리
@Query(value = "select m from Member m",
countQuery = "select count(m.username) from Member m")
Page<Member> findMemberAllCountBy(Pageable pageable);
inner, outer 조인시 카운트 쿼리도 조인하고 카운팅 하기 때문에 성능 문제 발생
따라서 count 쿼리를 분리하여 조회
페이지를 유지하면서 엔티티를 DTO로 변환 가능
Page의 Map 메서드를 사용
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
Page<Member> page = memberRepository.findByAge(age, pageRequest);
Page<MemberDto> dtoPage = page.map(MemberDto::new);
✔️ 벌크성 수정 쿼리
스프링 JPA에서 벌크성 수정 쿼리를 날릴때는
@Modifying 어노테이션을 추가로 붙여줘야한다.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
문제
벌크성 수정 쿼리는 영속성 컨텍스트를 무시하고 바로 DB에 보내기 떄문에
1차 캐시에 있는 기존 엔티티들은 변하지 않는다.
즉 벌크 연산 후 영속성 컨텍스트 엔티티에 접근하려면
해결 방안
벌크 쿼리를 날린 후
1. 영속성 컨텍스트를 초기화
2. @Modifying(clearAutomatically = true)
✔️ @EntityGraph
fetch 조인을 어노테이션으로 간단하게 하는 방법이다.
left outer join을 사용한다.
// team을 fetch 조인해서 조회
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
✔️ Hint & Lock
JPA 쿼리 힌트 (JPA 구현체에게 제공하는 힌트)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
읽기 전용으로 힌트를 준다.
이러면 영속성 컨텍스트에 실리지 않기에
더티 체킹도 불가능 하다.
읽기 전용이라서 성능 향상이 있다.
import org.springframework.data.jpa.repository.QueryHints
@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly",
value = "true")},
forCounting = true)
Page<Member> findByUsername(String name, Pageable pageable);
forCounting : 반환 타입으로 Page 인터페이스를 적용하면
추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용(기본값 true )
다만, DTO는 엔티티가 아니기에 영속성 컨텍스트의 1차 캐시에 스냅샷이 안만들어진다.
따라서 ReadOnly가 무의미 하다.
Lock
import org.springframework.data.jpa.repository.Lock
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);
락에 대해서는 추가적인 공부가 필요
아무튼 어노테이션으로 락을 걸 수 있다.
🔖 학습내용 출처
자바 ORM 표준 JPA 프로그래밍 - 기본편 / 김영한
김영한. (2021). 자바 ORM 표준 JPA 프로그래밍. 에이콘출판사
'Back-End > JPA' 카테고리의 다른 글
스프링 데이터 JPA - 분석 (0) | 2024.07.31 |
---|---|
스프링 데이터 JPA - 확장 기능 (0) | 2024.07.31 |
스프링 데이터 JPA - 기본 (0) | 2024.07.29 |
API 학습 내용 (0) | 2024.07.15 |
JPA - JPQL (0) | 2024.07.12 |