ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 03. Query Method
    BackEnd/Spring Data JPA 2021. 10. 25. 22:47
    반응형

    1. 메소드 이름으로 쿼리 생성

      Spring Data JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행합니다. 해당 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 반드시 변경해야 합니다. 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생합니다.

    // Spring Data JPA Repository
    public interface MemberRepository extends JpaRepository<Member, Long> {
      List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
    }
    
    // JPA Repository
    public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
      return em.createQuery("select m from Member m where m.username = :username 
    and m.age > :age")
               .setParameter("username", username)
               .setParameter("age", age)
               .getResultList();
    }
    • 조회: find…By ,read…By ,query…By get…By,

     

    Keyword Sample JPQL snippet
    Distinct findDistinctByLastnameAndFirstname select distinct …​ where x.lastname = ?1 and x.firstname = ?2
    And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
    Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
    Is, Equals findByFirstname
    findByFirstnameIs
    findByFirstnameEquals
    … where x.firstname = ?1
    Between findByStartDateBetween … where x.startDate between ?1 and ?2
    LessThan findByAgeLessThan … where x.age < ?1
    LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
    GreaterThan findByAgeGreaterThan … where x.age > ?1
    GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
    After findByStartDateAfter … where x.startDate > ?1
    Before findByStartDateBefore … where x.startDate < ?1
    IsNull, Null findByAge(Is)Null … where x.age is null
    IsNotNull
    NotNull
    findByAge(Is)NotNull … where x.age not null
    Like findByFirstnameLike … where x.firstname like ?1
    NotLike findByFirstnameNotLike … where x.firstname not like ?1
    StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
    EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
    Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
    OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
    Not findByLastnameNot … where x.lastname <> ?1
    In findByAgeIn(Collection<Age> ages) … where x.age in ?1
    NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
    True findByActiveTrue() … where x.active = true
    False findByActiveFalse() … where x.active = false
    IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstname) = UPPER(?1)
    • COUNT: count…By 반환타입 long
    • EXISTS: exists…By 반환타입 boolean
    • 삭제: delete…By, remove…By 반환타입 long
    • DISTINCT: findDistinct, findMemberDistinctBy
    • LIMIT: findFirst3, findFirst, findTop, findTop3

    2. JPA NamedQuery

    // Member.class
    @Entity
    @NamedQuery(
      name="Member.findByUsername",
      query="select m from Member m where m.username = :username")
    public class Member {
      ...
    }
    
    /* Spring Data JPA로 NamedQuery 사용
     * Query 를 생략하고 메서드 이름만으로 Named 쿼리를 호출할 수 있다.
     * Spring Data JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행
     * 만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.
     * 실무에서는 Named Query 보다 @Query를 사용해서 Repository 메서드에 쿼리를 직접 정의한다.
     */
    @Query(name = "Member.findByUsername")
    List<Member> findByUsername(@Param("username") String username);

     

    3. @Query, Repository 메소드에 쿼리 정의

    // @org.springframework.data.jpa.repository.Query 어노테이션을 사용
    // JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음
    public interface MemberRepository extends JpaRepository<Member, Long> {
      @Query("select m from Member m where m.username= :username and m.age = :age")
      List<Member> findUser(@Param("username") String username, @Param("age") int age);
    }
    
    // DTO로 조회(주의! DTO로 직접 조회 하려면 JPA의 new 명령어를 사용해야 한다.)
    @Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " +
             "from Member m join m.team t")
    List<MemberDto> findMemberDto();

     

    4. Parameter Binding

    /*
     * select m from Member m where m.username = ?0    //위치 기반
     * select m from Member m where m.username = :name //이름 기반
     */
    import org.springframework.data.repository.query.Param
    
    public interface MemberRepository extends JpaRepository<Member, Long> {
      @Query("select m from Member m where m.username = :name")
      Member findMembers(@Param("name") String username);
    }
    
    // 컬렉션 파라미터 바인딩 (Collection 타입으로 in절 지원)
    @Query("select m from Member m where m.username in :names")
    List<Member> findByNames(@Param("names") List<String> names);

     

    5. Return Type

    List<Member> findByUsername(String name);     //컬렉션
    Member findByUsername(String name);           //단건
    Optional<Member> findByUsername(String name); //단건 Optional
    
    /*
     * 컬렉션 
     *   > 결과 없음: 빈 컬렉션 반환
     * 단건 조회
     *   > 결과 없음: null 반환
     *   > 결과가 2건 이상: javax.persistence.NonUniqueResultException 예외 발생
    */

     

    6. Paging, Sort

    1) 파라미터

    • org.springframework.data.domain.Sort : 정렬 기능
    • org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort 포함)

    2) 특별한 반환 타입

    • org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는 페이징, 사용 시 count 쿼리 분리 검토
    • org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1조회)
    • List (자바 컬렉션): 추가 count 쿼리 없이 결과만 반

    3) 사용 예제

    • Page findByUsername(String name, Pageable pageable); //count 쿼리 사용
    • Slice findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
    • List findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
    • List findByUsername(String name, Sort sort);
    // MemberRepository.java
    public interface MemberRepository extends Repository<Member, Long> {
      Page<Member> findByAge(int age, Pageable pageable);
    }
    
    /* 페이징 조건과 정렬 조건 설정
     * 두 번째 파라미터로 받은 Pagable은 인터페이스다. 
     * 따라서 실제 사용 시 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 
     * 객체를 사용한다.
     *
     * PageRequest Parameter
     * 1) 현재 페이지, 2) 조회할 데이터 수 입력, +) 정렬 정보
     * 페이지는 0부터 시작한다.
     */
     
    @Test
    public void page() throws Exception {
      //given
      memberRepository.save(new Member("member1", 10));
      memberRepository.save(new Member("member2", 10));
      memberRepository.save(new Member("member3", 10));
      memberRepository.save(new Member("member4", 10));
      memberRepository.save(new Member("member5", 10));
      
      //when
      PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
      Page<Member> page = memberRepository.findByAge(10, pageRequest);
      
      //then
      List<Member> content = page.getContent(); //조회된 데이터
      assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
      assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수
      assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
      assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호
      assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
      assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
    }
    
    // 페이지를 유지하면서 엔티티를 DTO로 변환
    Page<Member> page = memberRepository.findByAge(10, pageRequest);
    Page<MemberDto> dtoPage = page.map(m -> new MemberDto());

     

    7. 벌크성 수정 쿼리

      벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용합니다. 사용하지 않으면 Exception이 발생합니다. 벌크성 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있습니다. 권장하는 방안은 다음과 같습니다.

      1) 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행합니다.

      2) 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트를 초기화 합니다.

    • 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화: @Modifying(clearAutomatically = true) (default: false)
    // 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;
    }
    
    // Spring Data JPA
    @Modifying
    @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
    int bulkAgePlus(@Param("age") int age);

     

    8. @EntityGraph

      연관된 엔티티들을 SQL 한번에 조회하는 방법입니다. Fetch Join의 간편 버전으로 LEFT OUTER JOIN을 사용합니다.

    // 공통 메서드 오버라이드
    @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> findByUsername(String username)
    
    //NamedEntityGraph 사용 방법
    //1) Member.java
    @NamedEntityGraph(name = "Member.all", attributeNodes =
    @NamedAttributeNode("team"))
    @Entity
    public class Member {}
    
    //2) Repository.java
    @EntityGraph("Member.all")
    @Query("select m from Member m")
    List<Member> findMemberEntityGraph();
    반응형

    'BackEnd > Spring Data JPA' 카테고리의 다른 글

    04. 사용자 정의 리포지토리  (0) 2021.10.26
    02. Spring Data JPA  (0) 2021.10.25
    01. 프로젝트 환경설정  (0) 2021.10.23

    댓글

Designed by Tistory.