[ 김영한 스프링 데이터 JPA #3 ] 쿼리 메서드 기능

김수호·2024년 6월 2일
0
post-thumbnail

🤔 스프링 데이터 JPA 의 공통 인터페이스 기능이 아닌, 특정 도메인에 특화된 기능을 사용하고 싶을 때는 어떻게 처리하면 좋을까? 예를 들어, findByUsername() 과 같이 회원의 이름을 기준으로 조회하고자 하는 경우 말이다.

이를 해결하기 위해 스프링 데이터 JPA 에서는 쿼리 메소드라는 기능을 제공한다.

쿼리 메소드 기능 3가지

  • ① 메소드 이름으로 쿼리 생성
    • 스프링 데이터 JPA 는 메소드 이름을 분석해서 JPQL 을 생성하고 실행한다.
      • ex) 조회: find...By, read...By, query...By, get...By
        • By 뒤에는 where 문에 적용될 조건들을 넣으면 된다. (아무것도 안넣으면 전체 조회)
        • ... 에는 식별하기 위한 내용(설명)이 들어가도 된다.
        • ex) Optional<Member> findOptionalByUsername(String username);
      • ex) 삭제: delete...By, remove...By
      • 참고) 쿼리 메서드 규칙은 공식 문서를 참고하자.
    • 주의) 엔티티의 필드명이 변경되면 메서드 이름도 꼭 함께 변경해야 한다. 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다. ( 참고로 이렇게 애플리케이션 로딩 시점에 오류를 인지할 수 있는 것은 스프링 데이터 JPA의 매우 큰 장점이다. )
    • 참고) 그런데 조건이 추가될수록 메서드명이 너무 길어지고 복잡해진다. 따라서 실무에서는 조건이 2~3개가 넘어가거나 복잡해지면 다른 방식(아래 ③)으로 해결하는게 좋다.
  • ② 메소드 이름으로 JPA NamedQuery 호출
    • JPA 의 NamedQuery 를 호출할 수 있다.
      • 스프링 데이터 JPA 는 선언한 "도메인 클래스 + .(점) + 메서드 이름" 으로 Named 쿼리를 찾아서 실행한다.
      • 만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략(위 ①)을 사용한다.
    • 장점) 애플리케이션 로딩 시점에 쿼리를 파싱해서 오류가 있는지 체크한다.
    • 참고) 스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다. 대신 @Query 를 사용해서 리파지토리 메소드에 쿼리를 직접 정의한다. (아래 ③)
  • ③ @Query 애노테이션을 사용해서 리포지토리 메소드에 쿼리 정의하기
    • 인터페이스 메서드에 JPQL 을 바로 정의할 수 있다.
    • 장점) 애플리케이션 로딩 시점에 쿼리를 파싱해서 오류가 있는지 체크한다.
      • JPA NamedQuery 처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다. (매우 큰 장점)
    • 참고) 위 ①은 파라미터가 증가하면 메서드 이름이 매우 지저분해진다. 따라서 @Query 기능을 사용해서 메소드 이름을 조금 심플하게 가져가고, 쿼리를 직접 정의해서 주로 사용한다.
      • 참고로 동적 쿼리의 경우는 QueryDSL 을 사용해서 처리한다.
  • 참고) 쿼리 생성 전략 우선순위: ③ -> ② -> ①
    • 필요하면 전략을 변경할 수 있지만 권장하지 않는다.

 

✔️ 참고

  • @Query 를 통해서, (엔티티 타입이 아닌) 값이나 DTO를 조회하는 방법
    • 단순히 값 하나를 조회 : 조회할 값을 지정하고 반환 타입을 잘 맞춰주면 된다.
      • 참고) JPA 값 타입( @Embedded )도 이 방식으로 조회할 수 있다.
    • DTO로 직접 조회 : DTO로 직접 조회하려면 JPA 의 new 명령어를 사용해야 한다. 그리고 DTO에 조회하고자 하는 데이터를 받는 생성자가 필요하다.
  • 파라미터 바인딩
    • 위치 기반 ( select t from Team t where t.name = ?0 )
    • 이름 기반 ( select t from Team t where t.name = :name )
    • 가급적 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자.
      • 위치 기반은 실수로 순서를 바꾸거나 하면, DB에 데이터가 잘못 처리되는 문제가 발생할 수 있다.
    • 참고) 컬렉션 파라미터 바인딩 ( Collection 타입으로 in 절을 지원한다. )
  • 반환 타입
    • 스프링 데이터 JPA 는 유연한 반환 타입을 지원한다.
      • 컬렉션 ( ex. List<Team> )
      • 단건 ( ex. Team )
      • 단건(Optional) ( ex. Optional<Team> )
      • 참고) 이 외에도 여러 타입들이 제공된다. 스프링 데이터 JPA 공식 문서를 확인하자.
    • 실무 주의) 컬렉션 조회시 결과가 없으면 null 이 아닌, 빈 컬렉션이 반환된다.
      • 따라서 null 체크 로직을 작성하는 것은 의미가 없다.
    • 실무 주의) 단건 조회시 결과가 없으면 null 이 반환된다.
      • 단건 조회시 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingleResult() 메서드를 호출한다. 그런데 이 메서드는 조회 결과가 없으면 jakarta.persistence.NoResultException 예외를 발생시키는데, 개발자 입장에서는 다루기가 상당히 불편하다. 따라서, 스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null 을 반환한다.
    • 실무 주의) 단건 조회시 결과가 2건 이상인 경우 jakarta.persistence.NonUniqueResultException 예외가 발생한다.
      • 참고) 스프링 데이터 JPA는 해당 예외를 스프링 예외 계층으로 바꿔서 던진다. ( org.springframework.dao.IncorrectResultSizeDataAccessException )

강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글