Back-End/JPA

스프링 데이터 JPA - 페이징과 정렬, 벌크 수정

Meluu_ 2024. 7. 29. 11:24

✔️  페이징과 정렬 


페이징과 정렬 파라미터

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