[자바 ORM 표준 JPA 프로그래밍] 12장. 스프링 데이터 JPA
by Jo
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(…): 모든 엔티티를 조회
- 정렬, 페이징 조건을 파라미터로 제공 가능
- save(S): 새로운 엔티티(식별자 값 null)은 저장, 이미 있으면(식별자 값 not null) 수정
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
을 조합할 수 있음- 명세들을 조립할 수 있도록 도와주는 클래스
Specifications
는Specification
들을 조립할 수 있도록 도와주는 클래스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 로 조회해서 사용할 필요 없이 바로 엔티티 객체 받아서 쓰면 됨
- 이 때 도메인 클래스 컨버터는 해당 엔티티와 관련된 레포지토리 사용해서 조회함
- ex)
코드
@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);
}
}
Subscribe via RSS