[SpringBoot] 스프링 데이터 JPA - 쿼리 메소드 기능

sorzzzzy·2022년 2월 15일
0

Spring Boot

목록 보기
3/3
post-thumbnail

쿼리 메소드 기능은 스프링 JPA가 제공하는 마법같은 기능 중 하나이다.

  1. 메소드 이름으로 쿼리 생성
  2. 메소드 이름으로 JPA NamedQuery 호출
  3. @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

세가지를 하나씩 알아보자.


🏷 메소드 이름으로 쿼리 생성

메소드 이름을 분석해 JPQL 쿼리를 실행하는 방법이다.
이름과 나이를 기준으로 회원을 조회한다고 가정하자.

순수 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 에서는,

List<Member> findByUsernameAndAgeGreaterThan(String username, int age);

이 단 한줄 만으로 실행시킬 수 있다.


스프링 데이터 JPA는 쿼리 메소드 기능을 제공한다.

  • 조회 : find...By, read...By, query...By, get...By
  • COUNT : count...By 반환타입 long
  • EXISTS: exists...By 반환타입 boolean
  • 삭제 : delete...By, remove...By 반환타입 long
  • DISTINCT : findDistinct, findMemberDistinctBy
  • LIMIT : findFirst3, findFirst, findTop, findTop3

더 자세한 조건과 기능은 스프링 데이터 JPA 공식 문서 를 참고하자.

📌 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 함께 변경해야 한다.
➡️ 이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것이 스프링 데이터 JPA의 매우 큰 장점이다.


🏷 NamedQuery

@NamedQuery(
        name = "Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)

@NamedQuery 어노테이션으로 멤버 클래스에 Named 쿼리를 정의한다.

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

JPA를 직접 사용해서 Named 쿼리를 호출할 수 있다.

public interface MemberRepository
extends JpaRepository<Member, Long> { //** 여기 선언한 Member 도메인 클래스
      List<Member> findByUsername(@Param("username") String username);
  }

위와 같이 스프링 데이터 JPA로 Named 쿼리 호출을 할 수도 있다.

📌 스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.
대신 @Query 를 사용해서 리포지토리 메소드에 쿼리를 직접 정의한다.


🏷 @Query - 리파지토리 메소드에 쿼리 정의

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

메소드에 JPQL 쿼리를 작성하여 정의할 수 있다.
@org.springframework.data.jpa.repository.Query 어노테이션을 사용한다.

실무에서 메소드 이름으로 쿼리를 생성하는 기능은 파라미터가 증가하면 메소드 이름이 매우 지저분해질 수 있다.
따라서 @Query 기능을 자주 사용한다.


DTO를 사용하여 직접 조회를 할 수도 있다.

package study.datajpa.repository;

import lombok.Data;

@Data
public class MemberDto {

    private Long id;
    private String username;
    private String teamName;

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

DTO를 생성하고,

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

직접 조회한다.

주의해야 할 점은 DTO로 직접 조회 하려면 JPA의 new 명령어를 사용해야 하는데 이 때 위치를 명확하게 표현해야 한다.


🏷 파라미터 바인딩

select m from Member m where m.username = ?0 //위치 기반 
select m from Member m where m.username = :name //이름 기반

파라미터 바인딩에는 크게 위치 기반과 이름 기반이 있다.

public interface MemberRepository extends JpaRepository<Member, Long> {
        @Query("select m from Member m where m.username = :name")
        Member findMembers(@Param("name") String username);
}

코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하는 것을 권장한다.

@Query("select m from Member m where m.username in :names")
    List<Member> findByNames(@Param("names") List<String> names);

컬렉션 파라미터 바인딩은 Collection 타입으로 in 절을 지원한다.


🏷 반환 타입

스프링 데이터 JPA는 유연한 반환 타입을 지원한다.

List<Member> findByUsername(String name); // 1. 컬렉션 
Member findByUsername(String name); // 2. 단건
Optional<Member> findByUsername(String name); // 3. 단건 Optional

만약 조회 결과가 많거나 없다면?

  • 컬렉션은 결과가 없을 때 빈 컬렉션을 반환한다.
  • 단건 조회는 결과가 없을 때 null 을 반환하고, 결과가 두 건 이상일 땐 javax.persistence.NonUniqueResultException 예외를 발생시킨다.

자세한 내용은 스프링 데이터 JPA 공식 문서를 참조하자.

profile
Backend Developer

0개의 댓글