Query Method

NamedQuery

메서드명 기반 JPQL 쿼리

@Query

스프링 데이터의 쿼리 생성 전략 및 사용

Query Method

쿼리 메서드는 리포지토리 인터페이스에 정의된 메서드를 통해 자동으로 JPQL을 생성하거나 데이터베이스 연산을 수행할 수 있도록 지원하는 기능으로

스프링 데이터 JPA가 메서드 이름을 기반으로 JPQL 쿼리를 자동으로 생성하는 방식(Query Derivation)과 @Query 어노테이션을 적용해서 개발자가 직접 JPQL을 생성하는 방식이 있음

NamedQuery

엔티티 클래스에 @NamedQuery 어노테이션을 사용해서 정적 쿼리를 선언하는 방식으로 JPA 표준임

정적 쿼리로 미리 준비된 쿼리를 사용하기 때문에 성능 최적화와 재사용성을 높일 수 있고

한 번 정의된 쿼리는 여러 곳에서 재사용할 수 있으며, 런타임에 동적으로 생성하는 쿼리보다 성능 면에서 유리할 수 있음

NamedQuery 예시

NamedQuery 정의

@Entity
@NamedQuery(
        name = "User.findByFirstName",
        query = "SELECT u FROM User WHERE u.firstName = :firstName"
)
public class User {
  @Id
  private Long id;
  private String firstName;
}

NamedQuery 사용

public interface UserRepository extends JpaRepository<User, Long> {

  // @Query 부분을 주석 처리해도 동작함
  @Query(name = "User.findByFirstName")
  List<User> findByFirstName(@Param("firstName") String firstName);
}

메서드명 기반 JPQL 쿼리(Query Derivation, Query Creation)

메서드 이름으로부터 JPQL을 자동 생성하는 방식은 메서드 이름에 포함된 키워드를 인식하여 각 키워드에 따라 JPQL 쿼리를 생성함

보통 find...by... 형식으로 메서드 이름을 선언함

쿼리 키워드

쿼리 빌더(스프링 데이터 JPA 내장)는 메서드 이름을 각각 subject와 predicate로 구분함

subject와 predicate 사이의 값은 고유 플래그를 명시하지 않는 이상 쿼리를 설명하는 것으로 간주되어 쿼리 생성에 영향을 끼치지 않음

쿼리 subject 키워드 목록

쿼리 predicate 키워드 목록

메서드명 기반 쿼리 사용 예시

public interface UserRepository extends Repository<User, Long> {

  // SELECT u FROM User u WHERE u.emailAddress = ?1 and u.lastname = ?2 쿼리로 변환됨
  List<User> findEmailAddressAndLastname(String emailAddress, String lastname);

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // 중복 제거
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // 대소문자 구분 X
  List<Person> findByLastnameIgnoreCase(String lastname);
  // 모든 속성에 대한 대소문자 구분 X
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // 정렬 조건 지정
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

@Query

@Query 어노테이션은 개발자가 명시적으로 메서드에 JPQL 쿼리 또는 네티이브 SQL을 정의하는 방식으로 복잡한 조건, 조인이나 서브 쿼리 등이 필요한 상황에서 사용함

메서드의 파라미터를 쿼리에 바인딩할 수 있으며 위치 기반 바인딩(?1)과 이름 기반 바인딩(:paramName) 모두 지원함

@Query 사용 예시

public interface UserRepository extends JpaRepository<User, Long> {

  // JPQL 쿼리 정의, 이름 기반 바인딩 사용
  @Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.age > :age")
  List<User> findByFirstNameAndAgeGreaterThan(@Param("firstName") String firstName, @Param("age") int age);
}
public interface UserRepository extends JpaRepository<User, Long> {

  // 네이티브 SQL 쿼리 정의
  @Query(value = "SELECT * FROM users WHERE first_name = ?1 AND age > ?2", nativeQuery = true)
  List<User> findByFirstNameAndAgeGreaterThanNative(String firstName, int age);
}

스프링 데이터의 쿼리 생성 전략 및 사용

  1. 애플리케이션 로드 시점에 @NamedQuery 어노테이션에 정의된 정적 쿼리를 파싱하여 EntityManager 내부 캐시에 저장함
  2. 리포지토리 인터페이스를 스캔해서 @Query 어노테이션이 정의된 경우 해당 메서드에 정의된 JPQL을 매핑
  3. 선언된 쿼리가 없다면 메서드 이름을 기반으로 쿼리를 동적으로 생성함(메서드 호출될 때 JPQL 생성)