CS 잡지식

@NamedQuery vs @Query( JPA vs Spring Data JPA )

JIN_YOUNG _KIM 2023. 5. 11. 16:01
@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로 해결됨.