12장. 스프링 데이터 JPA


12.1 스프링 데이터 JPA 소개

  • CRUD 처리를 위한 공통 인터페이스 제공
  • 리포지토리 개발 시 인터페이스만 작성하면 실행 시점에 구현 객체를 동적으로 생성, 주입
    • 데이터 접근 계층 개발 시 구현 클래스 없이 인터페이스만 작성해도 됨




12.2 스프링 데이터 JPA 설정

  • 필요 라이브러리
    • spring-data-jpa
설정
<!-- spring data JPA maven 라이브러리 설정 -->
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>1.8.0.RELEASE</version>
</dependency>
  • 환경설정
    • 애플리케이션 실행 시 basePackage 에 있는 레포지토리 인터페이스들 찾아서 해당 인터페이스 구현한 클래스 동적 생성 후 빈으로 등록함
    • 스프링 설정에 XML 사용하는 경우
      • <jpa:repositories> 추가
        • base-package 속성으로 레포지토리 검색할 패키지 위치 입력
    • 스프링 설정에 JavaConfig 사용하는 경우
      • org.springframework.data.jpa.repository.config.EnableJpaRepositories 어노테이션 추가
      • basePackages 속성에 리포지토리 검색할 패키지 위치 입력
코드
// 스프링 설정에 JavaConfig 사용하는 경우
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig() {

}
설정
<!-- 스프링 설정에 XML 사용하는 경우 -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jpa="http://www.springfraework.org/schema/data/jpa"
  xsi:schemeaLocation="http://www.springfraework.org/schema/beans
    http//www.springfraework.org/schema/beans/spring-beans.xsd
    http://www.springfraework.org/schema/data/jpa
    http://www.springfraework.org/schema/data/jpa/spring-jpa.xsd">

  <jpa:repositories base-package="jpabook.jpashop.respotiroy" />
</beans>




12.3 공통 인터페이스 기능

  • 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공
    • 위 인터페이스 상속받아 제네릭에 엔티티 클래스, 엔티티 클래스가 사용하는 식별자 타입 지정해서 쓰면 됨
코드
// JpaRepository 공통 기능 인터페이스
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
  ...
}

...

// JpaRepository 사용하는 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long> {
  ...
}

  • Repository, CrudRepository, PagingAndSortingRepository 는 Spring Data 가 공통으로 사용하는 인터페이스
  • Spring Data JPA 의 JpaRepository 는 여기에 추가로 JPA 특화 기능을 제공
  • Spring Data JPA 인터페이스의 주요 메소드는 아래와 같음 (T: 엔티티, ID: 엔티티 식별자 타입, S: 엔티티와 그 자식 타입)
    • save(S): 새로운 엔티티(식별자 값 null)은 저장, 이미 있으면(식별자 값 not null) 수정
      • 저장시엔 EntityManager.persist(), 수정시엔 EntityManager.merge() 호출
    • delete(T): 엔티티 하나 삭제
      • EntityManager.remove() 호출
    • findOne(ID): 엔티티 하나를 조회
      • EntityManager.find() 호출
    • getOne(ID): 엔티티를 프록시로 조회
      • EntityManager.getReference() 호출
      • DEPRECATED 됨
        • 대신 getById(ID) 사용
    • findAll(…): 모든 엔티티를 조회
      • 정렬, 페이징 조건을 파라미터로 제공 가능




12.4 쿼리 메소드 기능

  • 메소드 이름만으로 쿼리를 생성하는 기능
  • 인터페이스에 메소드 선언해두면 해당 메소드 이름으로 적절한 JPQL 쿼리 생성, 실행해줌
  • 쿼리 메소드 기능은 크게 다음 3가지
    • 메소드 이름으로 쿼리 생성
    • 메소드 이름으로 JPA NamedQuery 호출
    • @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의



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

// 이메일과 이름으로 회원 조회하는 쿼리 메소드 정의
public interface MemberRepository extends Repository<Member, Long> {
  List<Member> findByEmailAndName(String email, String name);
}

...

// 위 쿼리 메소드로 실행되는 JPQL
"SELECT m " +
"FROM Member m " +
"WHERE m.email = ?1 " +
" AND m.name = ?2";
  • 스프링 데이터 JPA 쿼리 생성 기능 키워드 및 예시
키워드 JPQL 예
And findByLastnameAndFirstname … WHERE x.lastname = ?1 AND x.firstname = ?2
OR findByLastnameOrFirstname … WHERE x.lastname = ?1 or x.firstname = ?2
Is
Equals
findByFirstname
findByFirstnameIs
findByFirstnameEquals
… WHERE x.firstname = ?1
Between findByStartDateBetween … WHERE x.startDate BETWEEN ?1 AND ?2
LessThan findByAgeLessThan … WHERE x.age < ?1>
LessThanEqual findByAgeLessThanEqual … WHERE x.age <= ?1
GreaterThan findByAgeGreaterThan … WHERE x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … WHERE x.age >= ?1
After findByStartDateAfter … WHERE x.startDate > ?1
Before findByStartDateBefore … WHERE x.startDate < ?1
IsNull findByAgeIsNull … WHERE x.age IS NULL
IsNotNull
NotNull
findByAgeIsNotNull
findByAgeNotNull
WHERE x.age NOT NULL
Like findByFirstnameLike … WHERE x.firstname LIKE ?1
NotLike findByFirstnameNotLike … WHERE x.firstname NOT LIKE ?1
StartingWith findByFirstnameStartingWith … WHERE x.firstname LIKE ?1
(paramter bound with appended %)
EndingWith findByFirstname EdingWith … WHERE x.firstname LIKE ?1
(paramter bound with prepended %)
Containing findByFirstnameContaining … WHERE x.firstname LIKE ?1
(parametr bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … WHERE x.age = ?1 ORDER BY x.lastname DESC
Not findByLastnameNot … WHERE x.lastname <> ?1
In findByAgeIn(Collection ages) … WHERE x.age IN ?1
NotIn infdByAgeNotIn(Collection ages) … WHERE x.age NOT IN ?1
True findByAcitveTrue() … WHERE x.active = true
Falce findByActiveFalse() … WHERE x.active = false
IgnoreCase findByFirstnameIgnoreCase … WHERE UPPER(x.firstname) = UPPDER(?1)



12.4.2 JPA NamedQuery

  • 쿼리 생성 기능 이용해서 메소드 만들면 우선 선언한 “도메인 클래스 + . + 메소드 이름”으로 Named 쿼리 찾음
    • 있으면 해당 쿼리 사용
    • 없으면 쿼리 생성 전략 사용해서 만듬
코드
// named query 정의
@Entity
@NamedQuery(
  name="Member.findByUsername", // {도메인 클래스}.{메소드 이름} 으로 named query 이름 설정
  query="SELECT m FROM Member m WHERE m.username = :username")
)
public class Member {
  ...
}

...

// Spring Data JPA 로 named query 호출
public interface MemberRepository extends JpaRepository<Member, Long> { // 여기 선언한 Member 도메인 클래스 사용
  // {위의 Member 도메인 클래스}.{아래 메소드 이름} = Member.findByUsername 으로 설정된 Named query 있나 먼저 찾음
  // 있으면 사용, 없으면 쿼리 메소드 생성 기능으로 만들어줌
  List<MembeR> findByUsername(@Param("username") String username);
}



12.4.3 @Query, 레포지토리 메소드에 쿼리 정의

  • @org.springframework.data.jpa.repository.Query 어노테이션 붙여서 직접 쿼리 정의 가능
  • 일종의 이름없는 Named 쿼리
  • 애플리케이션 실행 시점에 문법 오류 발견할 수 있음
  • 네이티브 SQL 사용하려면 @Query 메소드의 nativeeQuery 속성 true 로 설정
코드
// 메소드에 JPQL 쿼리 작성
public interface MemberRepository extends JpaRepository<Member, Long> {
  @Query("SELECT m FROM Member m WHERE m.username = ?1")
  Member findByUsername(String username);
}

...

// JPA 네이티브 SQL 지원
public interface MemberRepository extends JpaRepository<Member, Long> {
  @Query("SELECT m FROM Member m WHERE m.username = ?0", nativeQuery = true)
  Member findByUsername(String username);
}



12.4.4 파라미터 바인딩

  • 위치 기반, 이름 기반 파라미터 바인딩 둘 다 지원
    • SELECT m FROM Member m WHERE m.username = ?1 // 위치 기반
    • SELECT m FROM Member m WHERE m.username = :name // 이름 기반
  • 기본 값은 위치 기반
    • JPQL 에서는 1 부터, Native SQL 에서는 0 부터 시작
  • 이름 기반 파라미터 바인딩 쓰려면 @org.springframework.data.repository.query.Param(파라미터 이름) 어노테이션 파라미터에 붙여주면 됨
코드
public interface MemberRepository extends JpaRepository<Member, Long> {
  @Query("SELECT m FROM Member m WHERE m.username = :name")
  Member findByUsername(@Param("name") Stirng username);  // @Param으로 설정해준 name 파라미터로 username 바인딩 됨
}



12.4.5 벌크성 수정 쿼리

  • @org.springframework.data.jpa.repository.Modifying 어노테이션 사용
  • 벌크성 쿼리 실행 후 영속성 컨텍스트 초기화하게 하려면 clearAutomatically 속성 true로 설정하면 됨 (default는 false)
    • @Modifying(clearAutomatically = true)
코드
@Modifying
@Query("UPDATE Product p SET p.price = p.price * 1.1 WHERE p.stockAmount < :sotckAmount")
int bulkPriceUp(@Param("stockAmount") String stockAmount);



12.4.6 반환 타입

  • 결과 2 건 이상이면 컬렉션 인터페이스
    • List<Member> findByName(String name); // 컬렉션
    • 조회 결과 없으면 빈 컬렉션 반환
  • 단건이면 반환 타입을 지정
    • Member findByEmail(String email); // 단건
    • 조회 결과 없으면 null 반환
    • 반환 타입 지정했는데 결과 2 건 이상이면 예외 발생



12.4.7 페이징과 정렬

  • 쿼리 메소드에 페이징 및 정렬 기능 사용할 수 있도록 2가지 파라미터 제공
    • org.springfraework.data.domain.Sort: 정렬 기능
    • org.springframework.data.domain.Pageable: 페이징 기능(내부에 Sort 포함되어 있음)
  • Pageable 사용 시 반환 타입으로 List, org.springframework.data.domain.Page 사용 가능
    • 반환 타입으로 Page 사용하면 검색된 전체 데이터 건수 조회하는 COUNT 쿼리를 추가로 호출함
  • Pageable은 인터페이스 이므로 실제 사용시엔 해당 인터페이스 구현체임 org.springframework.data.domain.PageRequest 객체 사용
    • new PageRequest(현재 페이지, 조회할 데이터 수);
    • new PageRequest(현재 페이지, 조회할 데이터 수, new 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("Kim", pageRequest);

List<Member> members = result.getContent(); // 조회 결과 데이터
int totalPages = result.getTotalPages();  // 전체 페이지 수
boolean hasNextPage = result.hasNextPage(); // 다음 페이지 존재 여부
  • Page 인터페이스가 제공하는 메소드들은 아래와 같음
메소드 반환 타입 설명
getNumber() int 현재 페이지
getSize() int 페이지 크기
getTotalPages() int 전체 페이지 수
getNumberOfElements() int 현재 페이지에 나올 데이터 수
getTotalElements() long 전체 데이터 수
hasPreviousPage() boolean 이전 페이지 존재 여부
isFirstPage() boolean 현재 페이지가 첫 페이지 인지 여부
hasNextPage() boolean 다음 페이지 존재 여부
isLastPage() boolean 현재 페이지가 마지막 페이지 인지 여부
nextPageable() Pageable 다음 페이지 객체, 없으면 null
previousPageable() Pageable 이전 페이지 객체, 없으면 null
getContent() List 조회된 데이터
hasContent() boolean 조회된 데이터 존재 여부
getSort() Sort 정렬 정보



12.4.8 힌트

  • @org.springframework.data.jpa.repository.QueryHints 어노테이션 사용
    • JPA 구현체에게 제공하는 힌트
// 쿼리 힌트 예시
@QueryHints(value = {
  @QueryHint(name = "org.hibernate.readOnly", value="true")
}, forCounting = true)  // 반환 타입으로 Page 인터페이스 적용 시 추가 호출되는 COUNT 쿼리에도 힌트 적용할 지 설정하는 옵션. default값 true
Page<Member> findByName(String name, Pageable pageable);



12.4.9 Lock

  • @org.springframework.data.jpa.repository.Lock 어노테이션 사용
// Lock 예시
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByName(String name);




12.5 명세

  • 술어(predicate): 명세(specification)를 이해하기 위한 핵심
    • 단순히 참, 거짓으로 평가
    • AND, OR 같은 연산자로 조합 가능
    • ex) 데이터를 검색하기 위한 제약 조건 하나 하나를 술어로 볼 수 있음
    • 스프링 데이터 JPA는 org.springframework.data.jpa.domain.Specification 클래스로 정의
  • Specification 은 composite pattern으로 구성되어 여러 Specification 을 조합할 수 있음
    • 명세들을 조립할 수 있도록 도와주는 클래스
  • SpecificationsSpecification 들을 조립할 수 있도록 도와주는 클래스
    • where(), and(), or(), not() 메소드 제공
  • 명세 기능 사용하려면 org.springframework.data.jpa.repository.JpaSepcificationExecutor 인터페이스 상속
    • JpaSpecificationExecutor의 메소드들은 Specification을 파라미터로 받아서 검색 조건으로 사용
코드
// JpaSepcificationExecutor 인터페이스
public interface JpaSpecificationExecutor<T> {
  T findOne(Specification<T> spec);
  List<T> findAll(Specification<T> spec);
  Page<T> findAll(Specification<T> spec, Pageable pageable);
  List<T> findAll(Specification<T> spec, Sort sort);
  long count(Specification<T> spec);
}

...

// JpaSepcificationExecutor 상속
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
  ...
}

...

// Specification 정의 예제
public class OrderSpec {
  // memberName Specification를 anonymous class로 정의해서 리턴
  public static Specification<Order> memberName(final String memberName) {
    return new Specification<Order>() {
      // Specifcation 은 toPredicate만 정의하면 됨
      public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        if (StringUtils.isEmpty(memberName)) return null;

        Join<Order, Member> m = root.join("member", JoinType.INNER);
        
        return builder.equal(m.get("name"), memberName);
      }
    };
  }

  // isOrderStatus Specification를 anonymous class로 정의해서 리턴
  public static Specification<Order> isOrderStatus() {
    return new Specification<Order>() {
      public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        return builder.equal(root.get("status"), OrderStatus.ORDER);
      }
    };
  }
}

...

// Specification 사용 예제
public List<Order> findOrders(String name) {
  List<Order> result = orderRepository.findAll(
    where(memberName(name)).and(isOrderStatus())  // 위에서 정의한 Specification들을 Specifications 메소드들로 엮어서 검색 조건으로 사용
  );

  return result
}




12.6 사용자 정의 레포지토리 구현

  • 인터페이스로 자동 생성되는 구현체 외에 추가로 메소드를 직접 구현해야할 때 필요한 메소드만 딱 구현할 수 있음
    • 사용자 정의 인터페이스 작성
      • 직접 구현할 메소드 추가
    • 해당 사용자 정의 인터페이스를 구현한 클래스 작성
      • 이 때 구현 클래스 이름은 {레포지토리 인터페이스 이름}Impl 로 해야함
      • 다른 이름 쓰려면 repository-impl-postfix 속성을 변경하면 됨
    • 레포지토리 인터페이스에서 사용자 정의 인터페이스를 상속
코드
// 사용자 정의 인터페이스
public interface MemberRepositoryCustom {
  // 직접 작성할 메소드
  public List<Member> findMemberCustom();
}

...

// 사용자 정의 구현 클래스
public class MemberRepositoryImpl implements MemberRepositoryCustom { // 이름은 레포지토리 인터페이스 이름 + Impl
  @Override
  public List<Member> findMemberCustom() {
    ... // 구현
  }
}

...

// 사용자 정의 인터페이스 상속
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
  ...
}
설정
<!-- xml로 사용자 정의 구현 클래스 이름 규칙 -->
<repositories base-package="jpabook.jpashop.repository"
  repository-impl-postfix="Impl" /> 
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository",
  repositoryImplementationsPostfix = "Impl")




12.7 Web 확장

  • 스플링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편의기능들 제공
    • 식별자로 도메인 클래스를 바로 바인딩 해주는 도메인 클래스 컨버터 기능
    • 페이징과 정렬 기능



12.7.1 설정

  • org.springframework.data.web.config.SpringDataWebConfiguration 을 스프링 빈으로 등록
  • JavaConfig 사용시에는 @org.springframework.data.web.config.EnableSpringDataWebSupport 어노테이션 사용
  • 설정하면 도메인 클래스 컨버터와 페이징 정렬 위한 HandlerMethodArgumentResolver 가 스프링 빈으로 등록됨
    • 등록되는 도메인 클래스 컨버터는 org.springframework.data.repository.support.DomainClassConverter
<!-- xml로 web 확장 기능 활성화 -->
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
// JavaConfig 으로 web 확장 기능 활성화
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
  ...
}



12.7.2 도메인 클래스 컨버터 기능

  • HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체 찾아서 바로 바인딩
    • ex) /member/memberUpdateForm?id=1와 같은 url로 회원 정보 수정 요청오면 id 파라미터로 조회해서 엔티티 객체를 바인딩 해서 넘겨줌
    • 컨트롤러에서는 id 로 조회해서 사용할 필요 없이 바로 엔티티 객체 받아서 쓰면 됨
    • 이 때 도메인 클래스 컨버터는 해당 엔티티와 관련된 레포지토리 사용해서 조회함
코드
@Controller
public class MemberController {
  // URL: /member/memberUpdateForm?id=1
  @RequestMapping("member/memberUpdateForm")
  // 도메인 클래스 컨버터가 중간에서 request parameter로 넘어온 id로 Member 조회해서 컨트롤러에 변환된 객체를 넘겨줌
  public String memberUpdateForm(@RequestParam("id") Member member,Model model) {
    model.addAttribute("member", member);

    return "member/memberSaveForm";
  }
}

도메인 클래스 컨버터로 넘어온 엔티티는 컨트롤러에서 수정해도 DB 반영 안됨
=> 영속성 컨텍스트의 동작 방식 때문
OSIV 사용하지 않는 경우: 조회한 엔티티는 준영속. 따라서 변경 감지 X. 수정한 내용 반영하려면 merge 해야함 OSIV 사용하는 경우: 엔티티는 영속. 하지만 OSIV 특성상 컨트롤러와 뷰에서는 영속성 컨텍스트 플러시 안함. 수정한 내용 반영하려면 트랜잭션을 시작하는 서비스 레이어 호출해야 함. 해당 서비스 레이어 종료시 플러시 및 트랜잭션 커밋 일어나서 변경 내용 반영됨



12.7.3 페이징과 정렬 기능

  • HandlerMethodArgumentResolver 로 스프링 데이터의 페이징 및 정렬 기능 사용 가능
    • 페이징 기능: PageableHandlerMethodArgumentResolver
    • 정렬 기능: SortHandlerMethodArgumentResolver
  • 파라미터로 Pageable 받아올 수 있음
    • 요청 파라미터
      • page: 현재 페이지. 0 부터 시작
      • size: 한 페이지에 노출할 데이터 건수
      • sort: 정렬 조건
    • 예시 url: /members?page=0&size=20&sort=name,desc&sort=address.city
코드
@RequestMapping(value = "/members", method = RequestMethod.GET)
// 파라미터로 Pageable을 받음
public String list(Pageable pageable, Model model) {
  Page<Member> page = memberService.findMembers(pageable);
  model.addAttribute("members", page.getContent());
  return "memgbers/memberList";
}


접두사

  • 페이징 정보 둘 이상이면 접두사로 구분
  • 접두사는 @Qualifier 어노테이션 사용
  • {접두사명}_{요청 파라미터} 형태로 구분
// 예시: /members?member_page=0&order_page=1
public String list(
  @Quailfier("member") Pageable memberPageable,
  @Quailfier("order") Pageable orderPageable
) {
  ...
}


기본값

  • Pageable의 기본값은 page=0, size=20
  • 변경하려면 @PageableDefault 어노테이션 사용
@RequestMapping(value = "/mebers_page", method=RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = "name", direction = Sort.Direction.DESC) Pageable pageable ) {
  ...
}




12.8 스프링 데이터 JPA가 사용하는 구현체

  • 스프링 데이터 JPA 공통 인터페이스는 org.springframework.data.jpa.repository.support.SinmpleJpaRepository 클래스가 구현
// SimpleJpaRepository
@Repository // JPA 예외를 스프링이 추상화한 예외로 변환함
@Transactional(readOnly = true) // 데이터를 조회하는 메소드에서는 readOnly = true. 데이터 변경하지 않는 트랜잭션에서 이 옵션 쓰면 플러시 생략해서 성능 향상됨
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {

  // 데이터 등록, 수정, 삭제하는 메소드에 트랜잭션 처리 되어있음 
  // 따라서 서비스 레이어에서 트랜잭션 시작 안했으면 레포지토리에서 시작함
  // 서비스 레이어에서 시작 했으면 레포지토리는 전파받아서 그대로 사용
  @Transactional
  public <S extends T> S save(S entity) {

    // entity의 식별자로 새로운 엔티티인지 기존에 있던건지 판별
    // entity가 객체이면 null 일때, 자바 기본 타입이면 0 일때 새로운 엔티티로 판단
    // Persistable 인터페이스 구현해서 isNew 로직 변경할 수 있음
    if (entityInformation.isNew(entity)) {

      // 새로운 엔티티면 persist
      em.persist(entity);
      return entity;
    } else {

      // 이미 있는거면 merge
      return em.merge(entity);
    }
  }

  ...

}




12.10 스프링 데이터 JPA와 QueryDSL 통합

  • 스프링 데이터 JPA에선 아래 2가지로 QueryDSL 지원
    • org.springframework.data.queryDsl.QueryDslPredicateExecutor
    • org.springframework.data.queryDsl.QueryDslRepositorySupport



12.10.1 QueryDslPredicateExecutor 사용

  • 레포지토리에서 QueryDslPredicateExecutor 상속하면 레포지토리에서 QueryDSL 사용 가능
  • QueryDSL을 검색 조건으로 사용하며 스프링 데이터 JPA의 페이징과 정렬 기능도 함께 사용 가능
  • join, fetch 는 사용 불가
    • JPQL에서의 묵시적 조인은 가능
  • QueryDSL 기능 다 쓰려면 JPAQuery 직접 쓰거나 QueryDslRepositorySupport 사용해야 함
// QueryDslPredicateExecutor 상속
public interface ItemRepository extends JpaRepository<Item, Long>, QueryDslPredicateExecutor<Item> {

  ...

  // QueryDSL 사용 예제
  public Iterable<Item> getAllToysPriceOver10000Under20000() {
    QItem item = QItem.item;
    Iterable<Item> result = itemRepository.findAll(
      item.name.contains("toy").and(item.price.between(10000, 20000))
    );
  }
  ...
}



12.10.2 QueryDslRepositorySupport 사용

  • QueryDSL 모든 기능 쓰려면 JPAQuery 객체 직접 생성해서 사용해야 함
    • QueryDslRepositorySupport 상속받으면 좀더 편리하게 QueryDSL 사용 가능
// 사용자 정의 레포지토리
public interface CustomOrderRepository {
  public List<Order> search(OrderSearch orderSearch);
}

...

// 사용자 정의 레포지토리 구현 및 QueryDslRepostiroySupport 상속 예시
public class OrderRepositoryImpl extends QueryDslRepositorySupport implements CustomOrderRepository {
  public OrderRepositoryImpl() {
    super(Order.class);
  }

  @Override
  public List<Order> search(OrderSearch orderSearch) {
    QOrder order = QOrder.order;
    QMember member = QMember.member;

    JPQLQuery query = from(order);

    if (StringUtils.hasText(orderSearch.getMemberName())) {
      query
        .leftJoin(order.member, member)
        .where(member.name.contains(orderSearch.getMemberName()));
    }

    if (orderSearch.getOrderStatus() != null) {
      query
        .where(order.status.eq(orderSearch.getOrderStatus()));
    }

    return query.list(order);
  }
}