-
3. 쿼리 메소드 기능Spring-Boot/실전! 스프링 데이터 JPA 2023. 2. 22. 08:01
1. 메소드 이름으로 쿼리 생성
- 스프링 데이터 JPA가 제공하는 마법 같은 기능
* 쿼리 메소드 기능 3가지 *
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- `@Query` 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의
스프링데이터JPA
public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByUsernameAndAgeGreaterThan(String username,int age); }
- 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행
*쿼리 메소드 필터 조건*
- 스프링 데이터 JPA 공식 문서 참고
( https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation )
* 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능 *
- 조회 : find...By, read...By, query...By, get...By
ㆍ예:) findHelloBy 처림 ...에 식별하기 위한 내용(설명)이 들어가도 된다.
- COUNT : count...By 반환 타입 `long`
- EXISTS : exists...By 반환 타입 `boolean`
- 삭제 : delete...By, remove...By 반환타입 `long`
- DISTINCT : findDistinct, findMemberDistinctBy
- LIMIT : findFirst3, findFirst, findTop, findTop3
* 참고 : 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다.
- 그렇지 않으면 애플리케이션 시작하는 시점에 오류가 발생한다.
- 이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.
2. JPA NamedQuery
- JPA의 NamedQuery를 호출할 수 있음
3. @Query, 리포지토리 메소드에 쿼리 정의하기
- `@org.springframework.data.jpa.repository.Query` 어노테이션을 사용
- 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음
- JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)
* 참고 : 실무에서는 메소드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메서드 이름이 매우 지저분해진다.
- 따라서 `@Query` 기능을 자주 사용하게 된다.
4. @Query, 값, DTO 조회하기
*단순히 값 하나를 조회*
@Query("select m.username from Member m") List<String> findUsernameList();
- JPA 값 타입(`@Embedded`)도 이 방식으로 조회할 수 있다.
*DTO로 직접 조회*
@Query("select new study.datajpa.repository.MemberDto(m.id, m.username, t.name) from Member m join m.team t") List<MemberDto> findMemberDto();
- 주의! DTO로 직접 조회 하려면 JPA의 `new` 명령어를 사용해야 한다.
- 생성자가 맞는 DTO가 필요하다. ( JPA와 사용방식이 동일하다. )
5. 파라미터 바인딩
- 위치 기반
- 이름 기반
select m from Member m where m.username = ?1 #위치 기반 select m from Member m where m.username = :name #이름 기반
* 참고 : 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자 ( 위치기반은 순서 실수가 바꾸면... )
* 컬렉션 파라미터 바인딩 *
@Query("select m from Member m where m.username in :names") List<Member> findByNames(@Param("names") Collection<String> names);
6.반환 타입
- 스프링 데이터 JPA는 유연한 반환 타입 지원
List<Member> findByUsername(String name); // 컬렉션 Member findByusername(String name); // 단건 Optional<Member> findByUsername(String name); // 단건
* 조회 결과가 많거나 없으면? *
- 컬렉션
ㆍ결과 없음 : 빈 컬렉션 반환
- 단건 조회
ㆍ결과 없음: `null` 반환
ㆍ결과가 2건 이상 : `javax.persistence.NonUniqueResultException` 예외 발생
* 참고 : 단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 `Query.getSingleResult()` 메서드를 호출한다.
- 이 메서드를 호출했을 때 조회 결과가 없으면 `javax.persistence.NoResultException` 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다.
- 스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 `null`을 반환한다.
* 빈값일수도 있으면 그냥 Optional을 써라!
7. 순수 JPA 페이징과 정렬
8. 스프링 데이터 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 쿼리 없이 결과만 반환
*페이징과 정렬 사용 예제*
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);
* 주의 : Page는 1부터 시작이 아니라 0부터 시작이다.
* 실습 *
- Page
- Slice(count X) 추가로 limit + 1 을 조회한다. 그래서 다음 페이지 여부 확인 ( 최근 모바일 리스트 생각해보면 됨 )
- List ( count X )
- 카운트 쿼리 분리(이건 복잡한 sql에서 사용, 데이터는 left join, 카운트는 left join 안해도 됨 )
ㆍ실무에서 매우 중요!!!
* 참고 : 전체 count 쿼리는 매우 무겁다.
9. 벌크성 수정 쿼리
@Modifying(clearAutomatically = true) // 이게 있어야 .executeUpdate() 를 실행 @Query("update Member m set m.age = m.age +1 where m.age >= :age") int bulkAgePlus(@Param("age") int age);
- clearAutomatically : 영속성 컨텍스트를 초기화
10. @EntityGraph
- 연관된 엔티티들을 SQL 한번에 조회하는 방법
// 1. 페치조인 @Query("select m from Member m left join fetch m.team") List<Member> findmemberFetchJoin(); // 2. 상위 메서드 @EntityGraph @Override @EntityGraph(attributePaths = {"team"}) List<Member> findAll(); // 3. JPQL + @EntityGraph @EntityGraph(attributePaths = {"team"}) @Query("select m from Member m ") List<Member> findMemberEntityGraph(); // 4. 쿼리메소드 + @EntityGraph @EntityGraph(attributePaths = {"team"}) //@EntityGraph("Member.all") // NamedEntityGraph List<Member> findEntityGraphByUsername(@Param("username") String username);
- 간단하면 @EntityGraph를 쓰고 조금이라도 복잡해지면 JPQL을 쓴다.
11. JPA Hint & Lcok
- JPA 쿼리 힌트(SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true")) Member findReadOnlyByUsername(String username);
Member findMember = memberRepository.findReadOnlyByUsername("member1"); // 변경감지체크 X findMember.setUsername("member2"); // update X
- 진짜 트래픽이 많은 API 정도만 넣는다.
- 진짜 조회성능이 떨어지면 redis로 캐싱한다.
@Lock(LockModeType.PESSIMISTIC_WRITE) List<Member> findLockByUsername(String username);
- 실시간 트래픽이 많은 서비스에서는 lock을 걸면 안된다.
- select for update
'Spring-Boot > 실전! 스프링 데이터 JPA' 카테고리의 다른 글
6. 나머지 기능들 (0) 2023.02.24 5. 스프링 데이터 JPA 분석 (0) 2023.02.23 4. 확장 기능 (0) 2023.02.23 2. 공통 인터페이스 기능 (0) 2023.02.17 1. 프로젝트 환경 설정 & 2. 예제 도메인 모델과 동작 확인 (0) 2023.02.15