스프링 데이터 JPA는 메소드 이름만으로 쿼리를 자동으로 생성해주는 쿼리 메소드(Query Method) 기능을 제공합니다. 이를 사용하면 별도의 쿼리문 작성 없이도 간단하게 데이터를 조회할 수 있습니다. 쿼리 메소드 기능은 다양한 형태의 메소드를 지원하며, 이번 글에서는 몇 가지 예시를 살펴보겠습니다.
메소드 이름으로 쿼리 생성
메소드의 이름을 분석해서 JPQL 쿼리를 실행시켜 준다.
이름과 나이를 기준으로 회원을 조회하는 기능으로 살펴 보자.
스프링 데이터 JPA 예시
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
쿼리 메소드 이름 규칙
스프링 데이터 JPA의 쿼리 메소드 이름 규칙은 공식 문서에서 확인 할 수 있습니다.
해당 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 한다. 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.
이렇게 애플리케이션 로딩 시점에 오류을 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.
JPA NamedQuery
- Entity에 쿼리를 정의해서 해당 쿼리에 정의된 이름으로 호출하는 방법
- 스프링 데이터 JPA를 주로 사용하는 실무에서는 잘 사용하지 않고, 후술할
@Query
방법을 주로 사용한다.
- Entity에
@NamedQuery
정의
/* Entity Area */
@Entity
@NamedQuery(name="Member.findByUsername",
query="select m from Member m where m.username = :username"
)
public class Member {
...
}
- 순수 JPA를 이용한 NamedQuery 호출
public class MemberRepository {
public List<Member> findByUsername(String username) {
...
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
}
- 스프링 데이터 JPA를 이용한 NamedQuery 사용
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
→ @Query
생략가능(메서드 이름으로 먼저 조회를 하기 때문)
@Query, 리포지토리 메소드에 쿼리 정의하기
메서드에 JPQL 쿼리를 작성할 수 있다.
@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 어노테이션 사용
- 실행할 메서드에 정적 쿼리를 직접 작성하기에 익명 NamedQuery라 할 수 있다.
- JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다.
실무에서는 쿼리 메서드기능을 이용하여 기능 구현하는 것은 파라미터가 증가할수록 메서드명이 복잡해지므로, 해당
@Query
기능을 자주 사용한다.
값 조회하기
@Query("select m.username from Member m")
List<String> findUsernameList();
DTO로 직접 조회
@Query("select new study.datajpa.repository.MemberDto(m.id, m.username, t.name)" +
" from Member m join m.team t")
List<MemberDto> findMemberDto();
파라미터 바인딩
파라미터 바인딩은 위치기반 바인딩과 이름기 기반 바인딩 두가지가 존재한다.
select m from Member m where m.username = ?0 //위치 기반
select m from Member m where m.username = :name //이름 기반
이름기반 바인딩을 사용할 것!
- 위치기반을 사용할 경우, 어떤 파라미터가 매핑되는지 가독성이 떨어지고, 순서가 바뀔경우 에러가 날 확률이 높다.
컬렉션 파라미터 바인딩
Collection 타입으로 in절 지원
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
반환 타입
List<Member> findByUsername(String name); //컬렉션
Member findByUsername(String name); //단건
Optional<Member> findByUsername(String name); //단건 Optional
조회 결과가 없는 경우
- 컬렉션 : 빈 컬렉션 반환
- 단건 → null 반환
결과가 2건 이상일 경우
- 단건 →
javax.persistence.NonUniqueResultException
예외 발생
정리
기본적으로 메소드 이름으로 생성해주는 기능을 사용하게 되며, 쿼리 파라미터가 많아져서 메소드 명이 길어질 경우, @Query
를 이용해서 직접 쿼리를 작성해서 사용하면 된다.