(3) 쿼리 메소드 기능
- 스프링 데이터 JPA가 제공하는 마법같은 기능이다.
- 메소드 이름만으로 쿼리를 생성하는 기능이 존재한다.
인터페이스 메소드만 선언하면 메소드 이름으로 JPQL 쿼리를 생성해서 실행한다.
스프링 데이터 JPA가 제공하는 쿼리 메소드 기능
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 Repository 인터페이스에 쿼리 직접 정의
=> 이 기능들을 활용하면 인터페이스만으로 필요한 대부분의 쿼리 기능을 개발할 수 있다.
12.3.1 메소드 이름으로 쿼리 생성하기
정해진 규칙에 따라서 메소드 이름을 지어야 한다.
// 이메일과 이름으로 회원 조회public interface MemberRepository extends Repository<Member, Long> {List<Member> findByEmailAndName(String email, String name);}
위의 메소드를 실행하면 스프링 데이터 JPA는 메소드 이름
을 분석해서 다음과 같은 JPQL을 생성하고 실행한다.
select m from Member m where m.email = ?1 and m.name = ?2
- 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 꼭 함께 변경해야 한다.
- 그렇지 않으면 애플리케이션 시작 시점에 오류가 발생한다.
12.3.2 JPA NamedQuery
- 스프링 데이터 JPA는 메소드 이름으로 JPA Named 쿼리를 호출하는 기능 제공
- JPA Named 쿼리 : 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법 -> 애노테이션이나 XML 에 쿼리를 정의할 수 있다.
// @NamedQuery 애노테이션으로 Named 쿼리 정의@Entity@NamedQuery(name="Member.findByUsername",query="select m from Member m where m.username = :username")public class Member {...}
JPA를 직접 사용해서 Named 쿼리 호출
public class MemberRepository {public List<Member> findByUsername(String username) {...}}
스프링 데이터 JPA 로 Named 쿼리 호출
(도메인 클래스.메소드 이름)
으로 엔티티에 설정된 Named 쿼리를 찾아 실행함.
public interface MemberRepository extends JpaRepository<Member, Long> { // 여기 선언한 Member 도메인 클래스를 이용List<Member> findByUsername(@Param("username") String username); // @Param : 이름기반 파라미터 바인딩}
12.3.3 @Query, 리포지토리 메소드에 쿼리 정의
- 리포지토리 메소드에 직접 쿼리를 정의하려면 @Query 어노테이션을 사용한다.
- 실행할 메소드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라고 할 수 있다.
- 애플리케이션 실행 시점에 문법 오류를 발견할 수 있는 장점이 있다.
public interface MemberRepository extends JpaRepository<Member, Long>{@Query("select m from Member m where m.username = ?1") // 직접 정적 쿼리를 작성Member findByUsername(String username); // 이름 없는 Named 쿼리 작성법}
JPA 네이티브 SQL 지원
public interface MemberRepository extends JpaRepository<Member, Long> {@Query(value = "SELECT * FROM MEMBER WHERE USERNAME = ?0",nativeQuery = true)Member findByUsername(String username);}
파라미터 바인딩을 사용하면 JPQL 은 위치 기반 파라미터를 1부터 시작하지만
스프링 데이터 JPA가 지원하는 네이티브 SQL은 0부터 시작한다.
12.3.4 파라미터 바인딩
스프링 데이터 JPA 다음의 파라미터 바인딩 기법들을 제공한다.
위치 기반 파라미터 바인딩
기본값. 코드 가독성과 유지보수가 좋다.
select m from Member m where m.username =?1
이름 기반 파라미터 바인딩
@Param(파라미터 이름)
어노테이션을 사용한다.select m from Member m where m.username = :name
import org.springframework.data.repository.query.Parampublic interface MemberRepository extends JpaRepository<Member, Long> {@Query("select m from Member m where m.username = :name")Member findByUsername(@Param("name") String username); // :name 파라미터에 매핑}
12.3.5 벌크성 수정 쿼리
JPA 로 작성한 벌크성 수정 쿼리는
executeUpdate()
를 사용
스프링 데이터 JPA를 사용한 벌크성 수정 쿼리
// 벌크성 수정, 삭제 쿼리 시 사용하는 어노테이션@Modifying(clearAutomatically = true) // 벌크성 쿼리 실행 후 영속성 컨텍스트 초기화, 기본값은 false@Query("update Product p set p.price = p.price * 1.1 where p.stockAmount < :stockAmount")int bulkPriceUp(@Param("stockAmount") String stockAmount);
12.3.6 반환 타입
스프링 데이터 JPA는 유연한 반환 타입 지원
결과가 한 건 이상이면 컬렉션 인터페이스 사용
List<Member> findByName(String name);
결과가 단건이면 반환 타입을 지정
Member findByEmail(String email)
내부적으로 Query.getSingleResult()를 호출한다.
조회 결과가 없으면 예외가 발생하는데 스프링 데이터 JPA는 이런 경우, 예외를 무시하고 대신에 null 반환
조회 결과가 없을 경우
컬렉션은 빈 컬렉션 반환, 단건은 예외를 무시하고 null을 반환한다.
- 단건을 기대하고 반환타입 지정했는데 결과가 2건 이상 조회되면 예외 발생.
12.3.7 페이징과 정렬
쿼리 메소드에서 다음과 같은 파라미터를 사용할 수 있다.
페이징과 정렬 기능을 위한 파라미터
- org.springframework.data.domain.Sort : 정렬기능
- org.springframework.data.domain.Pageable : 페이징 기능(내부에 Sort 포함)
파라미터에
Pageable
을 사용하면 반환타입은List
나Page
를 사용 가능=> Page를 사용하면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출
// count 쿼리 사용Page<Member> findByName(String name, Pageable pageable);// count 쿼리 사용 안 함List<Member> findByName(String name, Pageable pageable);List<Member> findByName(String name, Sort sort);
Page 사용 예제 정의 코드
public interface MemberRepository extends Repository<Member, Long> {Page<Member> findByNameStartingWith(String name, Pageable Pageable);}
Page 사용 예제 실행 코드
//페이징 조건과 정렬 조건 설정PageRequest pageRequest =new PageRequest(0, 10, new Sort(Direction.DESC, "name"));Page<Member> result =memberRepository.findByNameStartingWith("김", pageRequest);List<Member> members = result.getContent(); //조회된 데이터int totalPage = result.getTotalPages(); //전체 페이지 수boolean hasNextPage = result.hasNextPage(); //다음 페이지 존재 여부=> 이름이 김으로 시작하는 회원 + 이름으로 내림차순 + 첫 번째 페이지부터 페이지당 보여줄 데이터는 10건
Pageable
은 인터페이스. 따라서 이를 구현한PageRequest
객체를 사용. 0부터 시작ex)
new PageRequest(현재 페이지, 조회할 데이터 수, [정렬 정보])
페이지는 0부터 시작한다.
Pageable
과Page
를 사용하면 지루하고 반복적인 페이지 처리를 손쉽게 개발 가능
12.3.8 Lock
쿼리 시 락을 걸려면 org.springframework.data.jpa.repository.Lock
어노테이션을 사용한다.
@Lock(LockModeType.PESSIMISTIC_WRITE)List<Member> findByName(String name);