ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

    댓글

Designed by Tistory.