
쿼리 메소드 기능은 스프링 JPA가 제공하는 마법같은 기능 중 하나이다.
@Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의세가지를 하나씩 알아보자.
메소드 이름을 분석해 JPQL 쿼리를 실행하는 방법이다.
이름과 나이를 기준으로 회원을 조회한다고 가정하자.
순수 JPA 에서는,
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 에서는,
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
이 단 한줄 만으로 실행시킬 수 있다.
스프링 데이터 JPA는 쿼리 메소드 기능을 제공한다.
find...By, read...By, query...By, get...Bycount...By 반환타입 longexists...By 반환타입 booleandelete...By, remove...By 반환타입 long findDistinct, findMemberDistinctBy findFirst3, findFirst, findTop, findTop3더 자세한 조건과 기능은 스프링 데이터 JPA 공식 문서 를 참고하자.
📌 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 함께 변경해야 한다.
➡️ 이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
@NamedQuery 어노테이션으로 멤버 클래스에 Named 쿼리를 정의한다.
public List<Member> findByUsername(String username) {
...
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
JPA를 직접 사용해서 Named 쿼리를 호출할 수 있다.
public interface MemberRepository
extends JpaRepository<Member, Long> { //** 여기 선언한 Member 도메인 클래스
List<Member> findByUsername(@Param("username") String username);
}
위와 같이 스프링 데이터 JPA로 Named 쿼리 호출을 할 수도 있다.
📌 스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.
대신@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 쿼리를 작성하여 정의할 수 있다.
@org.springframework.data.jpa.repository.Query 어노테이션을 사용한다.
실무에서 메소드 이름으로 쿼리를 생성하는 기능은 파라미터가 증가하면 메소드 이름이 매우 지저분해질 수 있다.
따라서 @Query 기능을 자주 사용한다.
DTO를 사용하여 직접 조회를 할 수도 있다.
package study.datajpa.repository;
import lombok.Data;
@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;
}
}
DTO를 생성하고,
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " +
"from Member m join m.team t")
List<MemberDto> findMemberDto();
직접 조회한다.
주의해야 할 점은 DTO로 직접 조회 하려면 JPA의 new 명령어를 사용해야 하는데 이 때 위치를 명확하게 표현해야 한다.
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);
}
코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하는 것을 권장한다.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
컬렉션 파라미터 바인딩은 Collection 타입으로 in 절을 지원한다.
스프링 데이터 JPA는 유연한 반환 타입을 지원한다.
List<Member> findByUsername(String name); // 1. 컬렉션
Member findByUsername(String name); // 2. 단건
Optional<Member> findByUsername(String name); // 3. 단건 Optional
만약 조회 결과가 많거나 없다면?
null 을 반환하고, 결과가 두 건 이상일 땐 javax.persistence.NonUniqueResultException 예외를 발생시킨다.자세한 내용은 스프링 데이터 JPA 공식 문서를 참조하자.