본 포스트는 김영한 님의 실전! 스프링 데이터 JPA 강의를 토대로 작성하였습니다.
지금까지는 순수 JPA를 이용하여 Repository를 직접 구현하고 사용했었다. 그러나 대부분의 CRUD가 하는 일이 비슷하다 보니 반복적인 코드가 많아졌고 이를 공통화하여 해결한 것이 Spring Data JPA 이다.
이번엔 기존의 제공하는 기능 말고 좀 더 세부적으로 쿼리를 만들 수 있는 기능들에 대해 알아보자.
제공하는 기능
Data 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();
jpql을 이용하여 멤버의 이름과 나이를 조건으로 데이터를 가져온다면 다음과 같이 만들 수 있다.
그러나, Data JPA에서 이를 구현하려면
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
관례에 따른 메소드 이름을 정해서 다음과 같은 메소드를 정의만 해두면 위와 같은 쿼리를 날리는 메소드가 만들어진다.
long
boolean
long
다음과 같이 정해진 관례에 따라 메소드 이름을 잘 정한다면 필요한 쿼리문을 메소드 이름만으로 구현해낼 수 있다.
그러나 위처럼 메소드 이름만 가지고 원하는 쿼리를 만들다보면 메소드 이름이 과하게 길어지게 되고 가독성이 떨어진다. 따라서 잘 쓰지는 않지만 Entity에 직접 이름을 가진 쿼리문을 지정할 수 있다.
@Entity
@NamedQuery(
name="Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
... }
다음과 같이 @NamedQuery 어노테이션을 이용하여 이름을 가진 쿼리를 정할 수 있는데, name 속성은 관례상 '엔티티.이름' 으로 많이 짓는다고 한다. query에는 실제 수행할 쿼리를 jpql로 적으면 된다.
// 순수 JPA 사용방법
public class MemberRepository {
public List<Member> findByUsername(String username) {
...
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
} }
순수 JPA에서 NamedQuery를 사용하려면 다음과 같이 사용하면 된다.
//Spring Data JPA 사용방법
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
스프링 데이터 JPA에서 사용하려면 다음과 같이 사용하면 된다.
조금 다른 점은 @NamedQuery에 정의된 쿼리에 username이라는 파라미터가 필요하므로 이를 메소드 인자에 @Param을 붙여서 파라미터로 넘겨주면 된다.
❗️ 참고 사항
위의 Spring Data JPA로 사용하는 방법에서 @Query(name = "Member.findByUsername") 은 생략 가능하다.
이유는 데이터 JPA에서 기본적으로 "도메인 클래스 + .(점) + 메서드 이름" 으로 Named 쿼리를 찾아서 실행한다.
즉, Member Entity에 적어둔 @NamedQuery의 name 속성이 Member.findByUsername이므로 Spring Data JPA에서 이를 찾을 수 있다.
만약 이 과정에서 찾지 못한다면 메소드 이름으로 쿼리를 생성하는 맨 처음 소개한 방법을 시도한다. 즉 사실 메소드 이름으로 쿼리를 만드려고 시도하기 전에 먼저 @NamedQuery로 만들어 둔 쿼리들 중 name이 같은 것이 있는지 먼저 확인하는 과정을 거친다.
다음과 같이 JPARepository를 상속받는 인터페이스에 정의한 메소드 위에 직접 실행할 쿼리를 적용할 수도 있다.
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);
}
다음과 같이 사용할 수 있는데, 쉽게 말해 이름 없는 @NamedQuery라고 할 수 있다.
위 예시처럼 엔티티 객체를 가져올 수도 있지만 필요한 경우 DTO를 반환할 수도 있다.
@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 클래스(필요한 값)" 으로 써주면 원하는 Dto 객체로 결과 값이 반환된다.
주의할 점은 해당 Dto 클래스에 쿼리 문에서 사용하는 생성자가 존재해야 하며 패키지 경로명을 반드시 적어주어야 한다는 점이다. (기존에 JPA에서 jpql을 사용하는 방법과 동일하다.)
Spring Data JPA에 @Query로 직접 쿼리를 적는 경우 파라미터 바인딩 하는 방법은 @Param을 이용하는 것이다.
import org.springframework.data.repository.query.Param
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
Member findMembers(@Param("name") String username);
}
이전에 @NamedQuery에서 파라미터 바인딩 하듯이 똑같이 하면된다. 현재 방식이 이름 없는 @NamedQuery를 사용하는 것이니까.
추가로 파라미터로 컬렉션도 넣을 수 있다.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
다음과 같이 Collection 타입인 List를 파라미터로 넘길 수 있다.
지금까지의 내용만 보더라도 스프링 데이터 JPA가 얼마나 많은 편리한 기능들을 제공하는지 알 수 있었다.
다음 포스트에서 정리할 내용들도 정말 개발 효율을 높여주는 기능들이 많다.
하지만, 김영한님도 강의에서 강조하듯, 결국 JPA에 대한 이해가 꼭 필요하다. 내부적인 원리를 이해하며 한층 더 깊이 이해하려 노력하자.