@NamedQuery vs @Query( JPA vs Spring Data JPA )
@Entity
@NamedQuery(
name="Member.findByUsername",
// JPQL을 [직접] 작성해야 한다.
// 이건 [정적] 메서드이다.
// => 고로, 애플리케이션 로딩 시점(컴파일 시점)에 아래 JPQL을 Parsing하여 만약
// 쿼리문에 오류가 있으면, 서버가 실행되지 않고, 애플리케이션 로딩 시점에 에러를 잡아줌.
// 이게 @NamedQuery의 막강한 장점이다.
query="select m from Member m where m.username = :username")
public class Member {
@Id@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
//이하 생략
}
// Repository 클래스 中
public List<Member> findByUsername(String username){
// createQuery x -> createNamedQuery o
return entityManager.createNamedQuery("Member.findByUsername",Member.class)
.setParameter("username",username)
.getResultList();
// 전체 조회
// 이러한 [쿼리] 메서드는 [동적] 메서드이기에, 아래 JPQL 쿼리문에 오류가 있어도 애플리케이션 로딩 시점(컴파일 시점)
// 에는 이거에 대한 오류 정보를 알려 주지 않기에 오류를 잡을 수가 없다.
// 클라이언트가 서버가 [실행 중] 일때 findAll() 메서드를 이용하게 되면, 그제서야 오류가 난다.
// -> 치면적인 [런타임] 에러가 발생한다!!!
public List<Member> findAll(){
return entityManager.createQuery("select m from Member m",Member.class)
.getResultList();
}
}
//JPA가 제공하는 @NamedQuery 사용하는 예시
@Test@Transactional
public void testNamedQuery() {
Member member1 = new Member("member1", 10);
Member member2 = new Member("member2", 20);
repository.save(member1);
repository.save(member2);
List<Member> findMember = repository.findByUsername("member1");
Member member = findMember.get(0);
assertThat(member).isEqualTo(member1);
}
-> [스프링 데이터], [스프링 데이터 JPA] 계층이 제공하지 않는 [쿼리] 메서드([조회] 메서드)를 위와 같이 [JPA]가 제공하는
@NamedQuery를 사용하여 구현할 수가 있다.
그러나 JPA가 제공하는 @NamedQuery는 실무에서 거의 사용되지 않는다고 한다.
왜냐하면, 2번재 코드를 보면 알겠지만, 개발자가 Repository 계층에서 해당 @NamedQuery를 이용하여 [쿼리] 메서드를
구현해야 한다는 번거로움이 있다.
그러나 [Spring Data JPA]가 제공하는 @Query를 이용하면, 개발자가 [쿼리] 메서드([조회] 메서드)를 구현하지 않아도
[자동]으로 구현을 해준다.
- @Query 이용 -
@Entity
@NamedQuery( // Member에 @NamedQuery는 필요하다!!!!!!!!
name="Member.findByUsername",
query="select m from Member m where m.username = :username")
@ToString(of = {"id", "username", "age"})
public class Member {
@Id@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
//이하 생략
}
public interface MemberReposiotry extends JpaRepository<Member,Long> {
@Query(name = "Member.findByUsername") // Spring Data JPA가 JPQL문을 [자동]으로 작성을 해준다.
// 매개변수의 username은 JPQL의 [:username]과 Binding된다.
List<Member> findByUsername(@Param("username") String usernmae); // 메서드 명은 아무거나로 지어도 됨.
}
@Test@Transactional
public void testNamedQuery() {
Member member1 = new Member("member1", 10);
Member member2 = new Member("member2", 20);
repository.save(member1);
repository.save(member2);
// [Spring Data JPA]가 제공하는 @Query로 쿼리 메서드 [자동] 생성!!!
List<Member> findMember = repository.findByUsername("member1");
Member member = findMember.get(0);
assertThat(member).isEqualTo(member1);
}
@Query 부분을 생략하여도 문제 없이 작동한다!!!!
public interface MemberReposiotry extends JpaRepository<Member,Long> {
//@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String usernmae); // 메서드 명은 아무거나로 지어도 됨.
}
Spring Data JPA의 쿼리 메서드 [우선 순위]
1. 공통 인터페이스의 엔티티, 즉 JpaRepository<Member,Long>의 Member에 점"."을 찍고, 메서드 명을 연결시킨 다음에
거기에 해당하는 @NamedQuery를 먼저 찾는다. 즉, Member.findByUsername의 @NamedQuery가 Member 클래스에 있
는지를 먼저 확인
2. @NamedQuery가 없으면, 메서드 이름을 보고, 쿼리 메서드를 만들어 준다.
-> 그러나, 위 2 개 방법 모두 강사는 추천하지 않는다고 한다.
Member 클래스와 MemberRepository 클래스를 왔다 갔다 하는 것도 번거롭고, Member 엔티티 위에 JPQL이 있는 것도
마음에 안 들고 등등......
그래서 Spring Data JPA가 제공하는 또다른 막강한 기능을 사용하여서, 개발자가 원하는 [쿼리] 메서드를 [자동] 생성하는
전략을 사용한다고 함(위에서 언급한 @NamedQuery의 장점을 [그대로] 이용하면서, 개발의 번거로움이라는 단점도 보완
하는 점에서 막강함)
- MemberRepository 메서드에 바로 JPQL을 때려 박음 -
public interface MemberReposiotry extends JpaRepository<Member,Long> {
@Query(name = "Member.findByUsername") // Spring Data JPA가 JPQL문을 [자동]으로 작성을 해준다.
List<Member> findByUsername(@Param("username") String usernmae); // 메서드 명은 아무거나로 지어도 됨.
// JPQL을 Member 클래스의 @NamedQuery가 아닌 [쿼리] 메서드(Repository 계층)위에 바로 적어 놓을 수가 있다.
// -> [정적]인 JPQL이므로, 애플리케이션 로딩 시점(컴파일 타임)에 쿼리문 에러 체크를 할 수가 있고,
// Member 클래스와 MemberRepsitory 인터페이스를 왔다 갔다가 하면서 개발하지 않아도 된다.
@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);
}
@Test@Transactional
public void testQuery() {
Member member1 = new Member("member1", 10);
Member member2 = new Member("member2", 20);
repository.save(member1);
repository.save(member2);
// [Spring Data JPA]가 제공하는 @Query로 쿼리 메서드 [자동] 생성!!!
List<Member> findMember = repository.findUser("member1",10);
Member member = findMember.get(0);
assertThat(member).isEqualTo(member1);
}
Q. 그럼, [동적]인 쿼리는 어떻게 애플리케이션 로딩 시점(컴파일 시점)에서 오류를 발견할 수가 있을까??
A. 그건 QueryDSL로 해결됨.