[Spring Data JPA] 쿼리 메서드 기능 ②

kiteB·2021년 10월 31일
0

Spring Data JPA

목록 보기
5/8
post-thumbnail

[ 반환 타입 ]

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

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

조회 결과가 없을 경우

  • 컬렉션: 빈 컬렉션 반환
  • 단건 조회: null 반환

조회 결과가 많을 경우

  • 단건 조회: javax.persistence.NonUniqueResultException 예외 발생

[ 순수 JPA 페이징과 정렬 ]

📌 페이징, 정렬 조건 예시

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

JPA 페이징 리포지토리

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.username 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();
}

테스트 실행 결과


[ 스프링 데이터 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 쿼리 없이 결과만 반환

1. 페이징과 정렬 사용 예제

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);

2. 예제 코드 작성

🔗 코드 확인하기

테스트 실행 결과

Slice 테스트 실행 결과

  • limit가 4로 나온다.

countQuery 테스트 실행 결과

  • left join 없음

[ 벌크성 수정 쿼리 ]

1. 순수 JPA 이용

public int bulkAgePlus(int age) {
    int resultCount = em.createQuery(
                    "update Member m set m.age = m.age + 1" +
                        "where m.age >= :age")
                .setParameter("age", age)
                .executeUpdate();
    return resultCount;
}

테스트 실행 결과

20살 이상인 사람만 age + 1하는 테스트 완료!


2. 스프링 데이터 JPA

@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
  • 벌크성 수정, 삭제 쿼리는 @Modifying이 있어야 한다.
    • 사용하지 않으면 예외 발생!
  • 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화 옵션: @Modifying(clearAutomatically = true)
    • default는 false

false로 설정하면

중간에 찍어보면 member5age41이 아닌 40으로 나온다. → bulk 연산 이후 영속성 컨텍스트를 날려버려야 한다!

🔼 이건 Entity Manager로 해결한 경우

🔼 이건 clearAutomatically 설정을 true로 한 경우

실행 결과


[ @EntityGraph ]

연관된 엔티티들을 SQL 한번에 조회하는 방법

memberteam은 지연로딩(LAZY) 관계이다.
따라서 다음과 같이 team의 데이터를 조회할 때마다 쿼리가 실행한다 (N+1 문제 발생)

@Test
public void findMemberLazy() throws Exception {
    //given
    //member1 -> teamA
    //member2 -> teamB

    Team teamA = new Team("teamA");
    Team teamB = new Team("teamB");
    teamRepository.save(teamA);
    teamRepository.save(teamB);

    Member member1 = new Member("member1", 10, teamA);
    Member member2 = new Member("member2", 10, teamB);
    memberRepository.save(member1);
    memberRepository.save(member2);

    em.flush();
    em.clear();

    //when
    List<Member> members = memberRepository.findAll();

    //then
    for (Member member : members) {
        System.out.println("member = " + member.getUsername());
    }
}

테스트 실행 결과

memberteam이 각각 조회된다.
→ 연관된 엔티티를 한번에 조회하려면 페치 조인이 필요하다.


JPQL fetch join

@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
  • 스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 사용하여 JPQL 없이 페치 조인을 사용할 수 있다.
  • JPQL + 엔티티 그래프도 가능하다!
//공통 메서드 오버라이드
@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> findEntityGraphByUsername(@Param("username") String username);

테스트 실행 결과

left outer join을 사용한다!


[ JPA Hint & Lock ]

1. JPA Hint

JPA 쿼리 힌트 (SQL 힌트가 아닌 JPA 구현체에게 제공하는 힌트)

@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
  • 쿼리를 읽기 전용으로 조회한다고 힌트를 주고 있다.

테스트 실행 결과


2. Lock

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String name);
  • 실시간 트래픽이 많은 곳에서는 Lock을 걸면 안된다.

테스트 실행 결과

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글