코딩과 결혼합니다

[Spring Data JPA] 쿼리 메서드 본문

2세/JPA

[Spring Data JPA] 쿼리 메서드

코딩러버 2024. 2. 28. 16:10
728x90

순수 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는 메서드 이름을 분석하여 JPQL을 생성하고 실행한다.

 public interface MemberRepository extends JpaRepository<Member, Long> {
 List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
 }

 

 

개발자가 쿼리를 짜지 않아도 위와 같이 데이터를 가져오므로 매우 편리하다. 

여기서 제공하는 쿼리 메서드 기능으로 대부분의 기능을 구현할 수 있다. 

 

✔️ 엔티티의 필드명이 변경되면 인터페이스에서 정의한 메서드 이름도 꼭 함께 변경한다! 그렇지 않으면 오류가 발생.

✔️ 메서드 이름을 분석하기 때문에 복잡해지면 메서드 명도 계속해서 늘어난다. 3개 이상의 파라미터를 사용해야 할 때에는 다른 방법을 고려해 보는 것을 권장한다.


JPA NamedQuery

쿼리 메서드 기능 중 named query가 있다. 이는 query에 이름을 부여하고 호출하는 것이다. (실무에서 거의 쓸 일은 없다.)

@Entity
 @NamedQuery(
        name="Member.findByUsername",
        query="select m from Member m where m.username = :username")
 public class Member {
    ...
 }
//순수 JPA repository

public class MemberRepository {
 public List<Member> findByUsername(String username) {
        ...
 List<Member> resultList = 
            em.createNamedQuery("Member.findByUsername", Member.class)
                .setParameter("username", username)
                .getResultList();
    }
 }

이런 식으로 사용할 수 있다. 

더 나가아 스프링 데이터 JPA에서는 이 네임드 쿼리를 편리하게 호출하는 기능을 제공한다.

//스프링 데이터 JPA repository

@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);

@Query(name = "Member.findByUsername") 이 부분을 생략해 정상 작동한다. Member Entity의 findByUsername이라는 명을 가진 네임드 쿼리를 먼저 찾고, 없으면 메서드 이름으로 쿼리를 생성해서 실행한다.

 

기존의 JPQL 문법의 경우에는 오타가 있을 경우에도 실행이 되어버리고, 나중에 고객이 기능을 누르는 순간 문법 오류가 터져버린다. 

 

네임드 쿼리는 애플리케이션 로딩 시점에 파싱을 하고 오류가 있으면 문법 오류를 알려준다.

네임드 쿼리는 기본적으로 정적 쿼리이기 때문에 쿼리들을 다 파싱 해볼 수 있어 JPQL을 SQL로 미리 다 만들어 놓을 수 있다. 그러한 과정을 거치면서 애플리케이션 로딩 시점에 버그를 잡을 수 있는 것이다.


@Query

 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);

}

 

실행할 메서드에 정적 쿼리를 직접 작성할 수 있다. (이름 없는 네임드 쿼리라 할 수 있다.)

때문에 네임드 쿼리와 같은 장점을 가진다.

 

기능이 간단할 경우에는 메서드 이름으로 쿼리를 생성하는 것을 쓰고 복잡해질 때에는 Query를 직접 정의해서 쓰고 

메시지 이름을 심플하게 가져가는 방법을 사용하는 것이 좋다.

 

(동적 쿼리를 사용할 때에는 Query DSL을 사용하면 된다. 이는 나중에 학습하며 다뤄보겠다.)


단순 엔티티조회 말고 값과 Dto를 조회하는 방법이다.

@Query("select m.username from Member m")
List<String> findUsernameList();

Member의 모든 username 가져오기.

//MemberRepository

@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();

==================================

@Data
public class MemberDto {

    private Long di;
    private String username;
    private String teamName;

    public MemberDto(Long di, String username, String teamName) {
        this.di = di;
        this.username = username;
        this.teamName = teamName;
    }
}

new operation 이라는 JPQL이 제공하는 문법이다. 생성자로 new를 하는 것처럼 적어 생성자와 매칭을 시켜준다.

객체를 생성해서 반환하는 것 같은 이 문법으로 Dto로 반환할 수 있다.