ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Data JPA - 쿼리 메서드 사용과 @Query
    Spring/Spring Data JPA 2022. 3. 19. 20:29
    728x90
    반응형

     

     

     

     Spring Data JPA에서 제공하는 공통 인터페이스는 우리가 기본적으로 사용하는 CRUD를 제공해준다. 즉, 순수하게 JPA를 이용한 Repository 클래스를 생성하여 CRUD 메서드들을 작성해왔던 번거로운 작업들을 Spring Data JPA에서 제공하는 공통 인터페이스인 JpaRepository를 이용하면 대신 구현 클래스를 생성해줘서 기본적인 CRUD를 작성하지 않아도 된다.

     

     

     JpaRepository의 상속 관계는 다음과 같다.

     

     

     


     

     

     

    주요 메서드 메서드

     

    주요 메서드 설명
    <S extends T> save(S entity) 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합
    void delete(T entity) 엔티티 하나를 삭제한다. EntityManager.remove() 호출
    Optional<T> findById(ID id) 엔티티 하나를 조회한다. EntityManager.find() 호출
    T getOne(Id id) 엔티티 프록시로 조회한다. ENtityManager.getReference() 호출
    List<S> findAll(...) 모든 엔티티를 조회. 정렬(Sort)이나 페이징(Pageable)조건을 파라미터로 제공 가능

     


     

    JpaRepository<T, ID> 사용 방법

     

     사용 방법은 매우 간단한다. Entity클래스 명 + Repository로 인터페이스를 생성한 후 JpaRepository를 상속받으면 된다. JpaRepository를 상속받으면 구현체인 SimpleJpaRepository가 생성되어 상속받은 모든 메서드들을 사용할 수 있게 된다.

     

    public interface MemberRepository extends JpaRepository<Member, Long> {
    
    }

     


     

    쿼리 메소드 기능

     

    • 메서드 이름을 분석하여 JPQL 쿼리를 실행해준다.

     

    스프링 데이터 JPA 공식 문서 - 쿼리 메서드 필터 조건

    Spring Data JPA - Reference Documentation

     

    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)

     

    메서드 이름으로 쿼리 생성

     

    • 간단한 JPQL 쿼리일 경우 쿼리 메서드를 이용함(복잡한 JPQL인 경우는 직접 JPQL 작성)
    • 조회 : find...By, read...By, query...By, get...By
             ex) findHelloBy처럼 ...에 식별하기 위한 내용(설명)이 들어가도 된다.
      스프링 데이터 JPA - 참조 문서 (spring.io)
    키워드 설명
    find…By
    read…By
    get…By
    query…By
    search…By
    stream…By
    조회
    exists…By 조회, boolean 반환
    count…By count 조회, long 반환
    delete…By
    remove…By
    삭제, long 반환
    …First<number>…
     …Top<number>…
    쿼리 결과를 제한
    findFirst3, findFirst, findTop, findTop3
    …Distinct… 중복 제외하여 조회

     

     

     

     

    순수 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();
    }

     

    스프링 데이터 JPA

    public interface MemberRepository extends JpaRepository<Member, Long> {
        List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
    }

     

     위 기능들은 엔티티의 필드명이 변경되면 인터페이스에 정의한 쿼리 메서드들도 이름도 꼭 함께 변경해줘야 한다. 즉, 스프링 데이터 JPA를 사용하면 애플리케이션 로딩 시점에 오류를 인지할 수 있는 장점이 있다. 


     

    JPA NamedQuery

     

    • @NamedQuery 어노테이션으로 Named 쿼리 정의할 수 있다.
    • 스프링 데이터 JPA는 선언한 "도메인 클래스 +.(점) + 메서드 이름"으로
      Named 쿼리를 찾아서 실행
    • 만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.
    • 잘 사용하지 않는다.(참고만 하자)

     

    @Entity
    @NamedQuery(
        name="Member.findByUsername",
        query="select m from Member m where m.username = :username")
    public class Member {
        ...
    }

     

     

    순수 JPA Repository에서 사용

    public class MemberRepository {
        public List<Member> findByUsername(String username) {
            ...
            List<Member> resultList =
            em.createNamedQuery("Member.findByUsername", Member.class)
                .setParameter("username", username)
                .getResultList();
        }
    }

     

    스프링 데이터 JPA에서 사용

    public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query(name = "Member.findByUsername")
        List<Member> findByUsername(@Param("username") String username);
    }

     위처럼 사용해도 되지만 @Query를 생략하고 메서드 이름만으로도 Named 쿼리를 호출할 수 있다.

    public interface MemberRepository extends JpaRepository<Member, Long> { //** 여기 선언한 Member 도메인 클래스
        List<Member> findByUsername(@Param("username") String username);
    }

     

     


     

    @Query - 리포지토리 메소드에 쿼리 정의

     

    • 스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.
    • 대신 @Query를 사용하여 Repository 메소드에 쿼리를 직접 정의한다.
    • 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있다.
    • 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);
    }

     

    @Query - 값, DTO 조회하기

     

    • 단순히 값 하나를 조회
    public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query("select m.username from Member m")
        List<String> findUsernameList();
    }

     

    • DTO로 직접 조회
    public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name)" +
     	"from Member m join m.team t")
        List<MemberDto> findMemberDto();
    }

     꼭 new 연산자를 사용해야하는 것을 명심하자. 또한 사용하는 DTO의 생성자가 필요하다.

    @Data
    public class MemberDto {
        private Long id;
        private String username;
        private String teamName;
        
        public MemberDto(Long id, String username, String teamName) {
            this.id = id;
            this.username = username;
            this.teamName = teamName;
        }
    }

     

    @Qeury - 파라미터 바인딩

     

    • 위치 기반 : 잘 사용하지 않는다.
             select m from Member m where m.username = ?0
    • 이름 기반
             select m from Member m where m.username = :name

     

    public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query("select m from Member m where m.username = :name")
        Member findMembers(@Param("name") String username);
    }

     

    @Qeury - 컬렉션 파라미터 바인딩

     

    • in 절에 Collection 타입을 사용할 수 있다.

     

    public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query("select m from Member m where m.username in :names")
        List<Member> findByNames(@Param("names") List<String> names);
    }

     

    @Query - 반환 타입

     

    • 스프링 데이터 JPA는 유연한 반환 타입을 지원한다.
    • 컬렉션
            - 결과 없음 : 빈 컬렉션 반환
    • 단건 조회
            - 결과 없음 : NULL 반환
            - 결과가 2건 이상 : javax.persistence.NonUniqueResultException 예외 발생

     

    public interface MemberRepository extends JpaRepository<Member, Long> {
        List<Member> findByUsername(String name); //컬렉션
        Member findByUsername(String name); //단건
        Optional<Member> findByUsername(String name); //단건 Optional
    }

     

     참고로 단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다. 이 메서드를 호출했을 때 조회 결과가 없으면 javax.persistence.NoResultException 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편한데 스프링 데이터 JPA는 조회 결과가 없을 시 발생하는 NoresultException 예외를 발생시키는 대신 null을 반환해준다.

     

     

    728x90
    반응형

    댓글

Designed by Tistory.