Spring Data JPA
는 메소드 이름을 분석하여 JPQL을 생성하고 실행한다.
동작 방식을 살펴보기 위해 순수 JPA코드와 Spring Data JPA 코드를 비교해보겠다.
//순수 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를 사용하여 username, age를 조건으로 구성한 JPQL이다.
//Spring Data JPA- 메소드 이름으로 쿼리 생성
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
//Spring Data JPA- 메소드 이름으로 쿼리 생성
List<Member> findTop3TestBy();
같은 동작을 하는 코드를 Spring Data JPA
로 작성한 코드이다.
findByUsernameAndAgeGreaterThan
이름의 메소드를 선언하게 되면 Spring Data JPA는
...where m.username=:username and m.age>:age
의 JPQL를 생성하게 된다.!
공식문서에 나와있는 쿼리 메소드 기능중 조회를 간단히 살펴보자면,
find...By = read...By 등
으로 메소드를 설계가 가능하다.
...
의 부분에는 메소드를 식별하기 위한 설명을 자유롭게 추가 가능하고 By
뒷부분부터는 조건문이 위치하게 된다.
만약 By
절 뒤에 아무것도 적지 않는다면 아무런 조건을 사용하지 않는것이다.
이밖에도,
많은 쿼리 메소드 기능을 제공한다. 아래 Spring 공식문서를 통해 자세히 볼수 있다.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
이처럼 Spring Data JPA
는 메소드 이름을 통해 정적 쿼리를 미리 정의할 수 있기 때문에 컴파일 시점에서 문법적인 오류를 조기에 발견할 수 있는 큰 장점이 있다!!!
하지만 조건으로 사용하는 필드가 많아지면, 메소드의 이름이 장황해지는 단점이 발생한다.
이를 해결할 수 있는 방식을 뒤에 소개해보겠다.
참고💡
조건문에 넣을 필드가 2이하까지는 "메소드 이름으로 쿼리 생성 방식"을 사용하고 초과할때부터는 뒤에 나오는 "@Query, 레포지토리 메소드 쿼리정의" 방식을 사용하는것이 좋다고 한다.
이 방식은 @NamedQuery
어노테이션을 사용하여 쿼리를 미리 정의해논후 이름을 할당해 해당 이름을 호출하여 사용하는 방식이다.
마찬가지로 순수 JPA 코드와 Spring Data JPA 코드를 비교하며 공부해보겠다.
...
@NamedQuery( //Named 쿼리
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
...
}
Entity에 @NamedQuery
를 사용하여 쿼리의 이름과 JPQL을 미리 선언하게 된다.
//순수 JPA - Named 쿼리 호출
public List<Member> findByUsername(String username){
return em.createNamedQuery("Member.findByUsername")
.setParameter("username", username)
.getResultList();
}
createNamedQuery
를 사용하여 미리 정의해논 쿼리이름을 호출한 후,
필요한 파라미터를 세팅해주는 방식으로 사용할 수 있다.
//Spring Data JPA - Named 쿼리 호출
@Query(name = "Member.findByUsername") //생략 가능
List<Member> findByUsername(@Param("username")String username);
@Query
어노테이션을 사용하여 쿼리문 이름을 호출할수 있게 된다.
또한 필요한 파라미터들은 @Param
어노테이션을 통해 매핑 시켜준다.
참고로
@Param("username")) -> "username"
이름과
select ... =:username" -> ":"username"
이름이 같아야지 정상적으로 파라미터 매핑이 된다.!
또한 위의 메소드에서는 @Query
어노테이션이 없어도 정상동작하게 된다.
왜냐하면,
Spring Data JPA
는 정의한 메소드의 JPQL문을 생성할때 아래와 같은 순서가 있기 때문이다.
@NamedQuery
를 먼저 찾는다. 메소드 이름으로 쿼리 생성
을 통해 JPQL을 생성한다.이 방식은 메소드 이름으로 쿼리 생성
과 마찬가지로 @NameQuery
를 통해 정적쿼리를 미리 정의하기 때문에 컴파일 시점에서 문법 오류를 발견할수 있는 장점이 있다.!!
하지만 "엔티티에 JPQL과 쿼리 이름을 미리 정의하고 정의한 이름을 호출"와 같이 번거로움이 많이 발생한다.
이러한 @NameQuery
의 장점과 단점을 보안한 방식이 다음 나올 방식이다.
이 방식은 @NamedQuery
방식처럼 엔티티에 미리 쿼리문을 생성하는것이 아니라,
레포지토리 메소드에 @Query
사용하여 바로 JPQL문을 작성할수 있는 방식이다.
//Spring Data JPA - 리포지토리 메소드에 쿼리 정의하기
@Query("select m from Member m where m.username= :username and m.age= :age")
List<Member> findMember(@Param("username") String username, @Param("age") int age);
보다시피 @Query
어노테이션에 JPQL문을 바로 작성할 수 있다.
실행할 메서드에 정적쿼리를 바로 작성하므로 이름 없는 Named 쿼리라고 할 수 있다.
실무에서 가장 많이 사용하는 방식이라고 한다.
정적 쿼리에 대해서 작성할때 파라미터가 소수일때는 "메소드 이름으로 쿼리생성" 방식을 사용하고 파라미터가 증가할때는 "@Query,레포지토리 메소드에 쿼리 정의"을 사용하면 될거 같다!
그 밖에도 DTO로 직접 조회하기, 파라미터 바인딩, 다양한 반환타입 등도 있다.
DTO 직접 조회 같은 경우에는 QueryDSL
방식이 더 편한거 같다.
(쿼리 결과를 DTO로 생성할때 생성자를 쓰는 방식이 QueryDSL이 더 편하기 때문에)
Spring Data JPA
가 내부적으로 try~ catch로 처리하여 null을 대신 반환해준다.