스프링 데이터 JPA가 제공하는 공통 메소드 이외에 특정 도메인에 특화된 기능을 구현할 때 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능을 사용한다. 이때 총 3가지 방법이 존재한다. 이번 포스팅에서 알아보자!
1. 메소드 이름으로 쿼리 생성
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();
}
예를 들어 이름과 나이를 조건으로 가지는 조회 로직을 만들기 위해서는 원래 위와 같이 JPQL문을 작성해주어야 한다.
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
...
}
하지만 쿼리 메소드 기능을 사용하면 MemberRepository 인터페이스 내에 해당 코드만 작성해주면 스프링 데이터 JPA가 메소드 이름을 분석하여 알아서 JPQL을 생성하고 실행한다. (정말 마법 같다..)
이때 필터 조건으로 사용할 수 있는 키워드들은 위 공식문서를 참고하길 바란다.
당연하게도 엔티티의 필드명이 변경되면, 인터페이스에 정의한 메소드 이름도 함께 변경해주어야 한다.
그렇지 않으면 어플리케이션을 시작하는 시점에 오류가 발생한다. 이 또한 스프링 데이터 JPA의 큰 장점이다!
2. JPA NamedQuery
@NamedQuery(
name="Member.findByUsername",
query="select m from Member m where m.username = :username"
)
public class Member {
...
}
다음과 같이 엔티티에 직접 JPQL 쿼리를 등록할 수 있다. 이때 이름은 관례상 도메인.클래스명을 따른다. 해당 쿼리는 어플리케이션 시작 시점에 오류를 잡아낼 수 있다. 이건 NamedQuery의 장점이다!
// 직접 JPA로 호출하는 방법
public List<Member> findByUsername(String username) {
return em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
// 스프링 데이터 JPA를 사용하여 호출하는 방법
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
직접 JPA을 사용하는 방법과, 스프링 데이터 JPA을 사용하여 NamedQuery를 호출하는 코드를 각각 작성했다.
이때 아래 코드에서는 이전과 달리 @Param("username")을 사용하였다. 왜냐하면 명확하게 JPQL을 작성했을 때는 NamedParameter가 넘어가야 한다. 또한 @Query(name = "Member.findByUsername")을 통해 호출될 NamedQuery을 지정해주었는데 사실 이건 작성해주지 않아도 정상적으로 실행된다. 스프링 데이터 JPA가 관례상 도메인클래스 + . + 메소드명 (여기서는 Member.findByUsername)으로 NamedQuery을 찾아서 실행한다. 물론 실행할 NamedQuery가 없다면 앞서 설명한 메소드 이름으로 쿼리 생성 전략을 따른다.
여담이지만 JPA NamedQuery는 실무에서 잘 사용하지 않는다고 한다. 왜냐하면 다음에 나올 친구가 너무 강력하기 때문,,!
3. @Query - 레포지토리 메소드에 쿼리 정의하기
@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);
실행할 메소드에 정적 JPQL 쿼리를 직접 작성해준다. 마찬가지로 @Param을 통해 파라미터를 넘겨줘야 한다. 또한 어플리케이션 실행 시점에 문법 오류를 발견할 수 있다는 매우 큰 장점이 있다!
@Query("select m.username from Member m")
List<String> findUsernameList();
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
엔티티 조회 뿐만 아니라 단순히 값 하나를 조회하거나 DTO를 조회하는 경우에도 사용할 수 있다.
추가적인 내용은 더보기를 눌러서 확인하면 된다!
보너스1. 파라미터 바인딩
@Query("select m from Member m where m.username = :name") // 이름 기반
Member findMembers(@Param("name") String username);
@Query("select m from Member m where m.username = ?0") // 위치 기반
Member findMembers(String username);
JPQL 쿼리의 파라미터를 바인딩할 때 이름 기반/위치 기반 2가지 방법이 있다. 하지만 웬만해서는 항상 이름 기반을 사용하자!
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
또한 컬렉션 타입 파라미터 바인딩을 통한 IN절도 지원한다.
보너스2. 반환 타입
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-return-types
스프링 데이터 JPA는 기본적으로 유연한 반환 타입을 지원한다. (해당 공식문서 참고)
List<Member> findByUsername(String name); // 컬렉션
Member findByUsername(String name); // 단건
Optional<Member> findByUsername(String name); // 단건 Optional
만약 위와 같이 단건 또는 컬렉션을 조회했는데 값이 없거나 많으면 아래와 같은 결과를 반환한다.
단건 조회 | 컬렉션 조회 | |
결과 없음 | null 반환 | 빈 컬렉션 반환 |
결과가 2건 이상 | X | javax.persistence.NonUniqueResultException 예외 발생 |
사실 단건 조회했을 때 결과가 없다면 NoResultException 예외가 발생하지만 스프링 데이터 JPA가 편의성을 위해 null로 변환한다.
'Develop > SPRING FRAMEWORK' 카테고리의 다른 글
Spring MVC가 제공하는 기능 (어노테이션 기반) (0) | 2021.08.18 |
---|---|
Sprinv MVC 기본 구조 알아보기 (0) | 2021.08.17 |
Spring Data JPA를 활용한 페이징과 정렬 기능 구현 (0) | 2021.07.30 |
API 개발 - 컬렉션 조회 성능 최적화 (0) | 2021.07.29 |
API 개발 - 지연 로딩과 조회 로직 성능 최적화 (0) | 2021.07.29 |