BOOK 3 - 자바 ORM 표준 JPA 프로그래밍(10)-2
객체 지향 쿼리 언어 - Criteria & QueryDSL
Criteria
- Criteria는 JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API다.
- 문자가 아닌 코드로 JPQL을 작성하므로 문법 오류를 컴파일 타임에 잡을 수 있고, 동적 쿼리를 안전하게 생성할 수 있다.
- 코드가 복잡해서 직관적인 이해가 힘들다.
Criteria 기초
- Criteria API는
javax.persistence.criteria패키지에 있다.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
cq.select(m);
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
em.getCriteriaBuilder()- Criteria 쿼리를 생성하려면 먼저
CriteriaBuilder를 얻어야 한다. EntityManager나EntityManagerFactory에서 얻을 수 있다.
- Criteria 쿼리를 생성하려면 먼저
cb.createQuery(Member.class)CriteriaBuilder에서CriteriaQuery를 생성한다.- 반환 타입을 지정할 수 있다.
cq.from(Member.class)- FROM 절을 생성한다.
- 반환된
m값은 Criteria에서 사용하는 특별한 별칭이다.- 조회의 시작점이라는 의미로 쿼리 루트(
Root)라 한다.
- 조회의 시작점이라는 의미로 쿼리 루트(
cq.select(m)- SELECT 절을 생성한다.
-
완성된 Criteria 쿼리를
createQuery()메소드에 넘겨주기만 하면 된다. where조건과order by조건을 추가해보자.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Member.class);
Root<Member> m = cq.select(Member.class);
Predicate usernameEqual = cb.equal(m.get("username"), "member1");
Order ageDesc = cb.desc(m.get("age"));
cq.select(m)
.where(usernameEqual)
.orderBy(ageDesc);
List<Member> resultList = em.createQuery(cq).getResultList();
m.get("username")m은 회원 엔티티의 별칭이다. 즉,m.username과 같은 표현이다.
cb.equal(m.get("username"), "member1")- JPQL에서
m.username = 'member1'과 같은 표현이다.
- JPQL에서
cb.desc(m.get("age"))- JPQL에서
m.age desc와 같은 표현이다.
- JPQL에서
- 만든 조건을
where,orderBy에 넣어서 원하는 쿼리를 생성한다.
쿼리 루트와 별칭
- 쿼리 루트는 조회의 시작점이다.
- Criteria에서 사용되는 특별한 별칭이다.
- 별칭은 엔티티에만 부여할 수 있다.
m.get("team").get("name")과 같이 경로 표현식을 사용할 수도 있다.
Root<Member> m = cq.from(Member.class);
Predicate ageGt = cb.greaterThan(m.<Integer>get("age"), 10);
cq.select(m)
.where(ageGt)
.orderBy(cb.desc(m.get("age")));
m.get("age")에서는age의 타입 정보를 알지 못하기 때문에 generic으로 반환 타입 정보를 명시해준다.greaterThan(A, B)메서드는A > B와 같은 표현이며gt()메서드를 사용할 수도 있다.
Criteria 쿼리 생성
-
Criteria를 사용하려면
CriteriaBuilder.createQuery()메서드로 Criteria 쿼리를 생성하면 된다. CriteriaQuery를 생성할 때 반환 타입을 지정하면em.createQuery()에서 반환 타입을 지정하지 않아도 된다.- 반환 타입을 지정할 수 없거나 반환 타입이 둘 이상이면
Object나Object[]로 반환받으면 된다.
조회
조회 대상을 한 건, 여러 건 지정
-
select에 조회 대상을 하나만 지정하려면 다음처럼 작성한다.cq.select(m); -
조회 대상을 여러 건 지정하려면
multiselect를 사용한다.cq.multiselect(m.get("username"), m.get("age"));cb.array를 사용해도 된다.
CriteriaBuilder cb = em.getCriteriaBuilder(); cq.select(cb.array(m.get("username"), m.get("age")));
DISTINCT
distinct는select다음에distinct(true)를 사용하면 된다.
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Member> m = cq.from(Member.class);
cq.multiselect(m.get("username"), m.get("age")).distinct(true);
TypeQuery<Object[]> query = em.createQuery(cq);
List<Object[]> resultList = query.getResultList();
NEW, construct()
- JPQL에서
select new 생성자()구문을 Criteria에서는cb.construct(클래스 타입, 생성자 파라미터)로 사용한다.
CriteriaQuery<MemberDTO> cq = cb.createQuery(MemberDTO.class);
Root<Member> m = cq.from(Member.class);
cq.select(cb.construct(MemberDTO.class, m.get("username"), m.get("age")));
TypedQuery<MemberDTO> query = em.createQuery(cq);
List<MemberDTO> resultList = query.getResultList();
- JPQL에서는
new로 사용자 지정 클래스의 인스턴스를 생성할 때 패키지명을 포함한 클래스 명을 작성해야 했지만, Criteria는 코드를 직접 다루므로 패키지명을 포함하지 않아도 된다.
튜플
- Criteria는 Map과 비슷한 Tuple 이라는 특별한 반환 객체를 제공한다.
cb.createTupleQuery()또는cb.createQuery(Tuple.class)로 Criteria를 생성한다.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Member> m = cq.from(Member.class);
cq.multiselect(
m.get("username").alias("username"),
m.get("age").alias("age")
);
TypedQuery<Tuple> query = em.createQuery(cq);
List<Tuple> resultList = query.getResultList();
for(Tuple tuple : resultList) {
String username = tuple.get("username", String.class);
Integer age = tuple.get("age", Integer.class);
}
- 튜플은 튜플의 검색 키로 사용할 튜플 전용 별칭을 필수로 할당해야 한다.
- 별칭은
alias()메서드로 지정할 수 있다.
- 별칭은
- 선언해둔 별칭으로 튜플에서 데이터를 조회할 수 있다.
- 튜플은 이름 기반이므로 순서 기반의
Obejct[]보다 안전하다. - 튜플은 엔티티도 조회할 수 있다.
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Member> m = cq.from(Member.class);
cq.select(cb.tuple(
m.alias("m"),
m.get("username").alias("username")
));
TypedQuery<Tuple> query = em.createQuery(cq);
List<Tuple> resultList = query.getResultList();
for(Tuple tuple : resultList) {
Member member = tuple.get("m", Member.class);
String username = tuple.get("username", String.class);
}
cq.multiselect(...)와cq.select(cb.tuple(...))은 같은 기능을 한다.
집합
GROUP BY
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Member> m = cq.from(Member.class);
Expression maxAge = cb.max(m.<Integer>get("age"));
Expression minAge = cb.min(m.<Integer>get("age"));
cq.multiselect(m.get("team").get("name"), maxAge, minAge);
cq.groupBy(m.get("team").get("name"));
TypedQuery<Object[]> query = em.createQuery(cq);
List<Object[]> resultList = query.getResultList();
HAVING
cq.multiselect(m.get("team").get("name"), maxAge, minAge)
.groupBy(m.get("team").get("name"))
.having(cb.gt(minAge), 10);
having(cb.gt(minAge), 10)은having min(m.age) > 10과 같다.
정렬
cb.desc()또는cb.asc()로 생성할 수 있다.
cq.select(m)
.where(ageGt)
.orderBy(cb.desc(m.get("age")));
- 여러개의 정렬 조건을 포함하고 싶을 때
orderBy()메서드에List<Order>타입의 파라미터를 전달할 수도 있다.
조인
-
조인은
join()메서드와JoinType클래스를 사용한다.public enum JoinType { INNER, // 내부 조인 LEFT, // LEFT OUTER JOIN RIGHT // RIGHT OUTER JOIN }
Root<Member> m = cq.from(Member.class);
Join<Member, Team> t = m.join("team", JoinType.INNER);
cq.multiselect(m, t)
.where(cb.equal(t.get("name"), "teamA"));
-
쿼리 루트
m에서 바로m.join("team")메소드를 호출해서 회원과 팀 테이블을 조인했다.- 조인한
team에는t라는 alias를 주었다.
- 조인한
-
조인 타입을 생략하면 기본 값으로 내부 조인을 사용한다.
-
페치 조인은
fetch()메서드를 사용한다.Root<Member> m = cq.from(Member.class); m.fetch("team", JoinType.LEFT); cq.select(m);
서브 쿼리
간단한 서브 쿼리
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
SubQuery<Double> subQuery = mainQuery.subquery(Double.class);
Root<Member> m2 = subQuery.from(Member.class);
subQuery.select(cb.avg(m2.<Integer>get("age")));
Root<Member> m = mainQuery.from(Member.class);
mainQuery.select(m)
.where(cb.ge(m.<Integer>get("age"), subQuery));
mainQuery.subquery(...): 서브 쿼리를 생성한다.where(..., subQuery): 생성한 서브 쿼리를 메인 쿼리의where절에서 사용한다.
상호 관련 서브 쿼리
- 서브 쿼리에서 메인 쿼리의 정보를 사용하려면 메인 쿼리에서 사용한 별칭을 얻어야 한다.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
Root<Member> m = mainQuery.from(Member.class);
SubQuery<Team> subQuery = mainQuery.subquery(Team.class);
Root<Member> subM = subQuery.correlate(m);
Join<Member, Tema> t = subM.join("team");
subQuery.select(t)
.where(cb.equal(t.get("name"), "teamA"));
mainQuery.select(m).where(cb.exists(subQuery));
List<Member> resultList = em.createQuery(mainQuery).getResultList();
correlate()메서드를 사용해서 메인쿼리의 별칭을 서브 쿼리에서 사용할 수 있다.- 위 코드에서 생성한 JPQL은 다음과 같다.
select m from Member m
where exists (select t from m.team t where t.name = 'teamA')
IN 식
- IN 식은 Criteria 빌더에서
in()메서드를 사용한다.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
cq.select(m)
.where(cb.in(m.get("username"))
.value("member1")
.value("member2"));
CASE 식
- CASE 식에는
selectCase()메서드와when(),otherwise()메서드를 사용한다.
Root<Member> m = cq.from(Member.class);
cq.multiselect(
m.get("username"),
cb.selectCase()
.when(cb.ge(m.<Integer>get("age"), 60), 600)
.when(cb.le(m.<Integer>get("age"), 15), 500)
.otherwise(1000)
)
파라미터 정의
CriteriaBuilder cb = em.getCriteriaBuilder();
CrietriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
cq.select(m)
.where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam")));
List<Member> resultList = em.createQuery(cq)
.setParameter("usernameParam", "member1")
.getResultList();
cb.paramter(파라미터 타입, 파라미터 이름): 쿼리에 사용할 파라미터 정의한다.setParameter(파라미터 이름, 파라미터 값): 해당 파라미터에 사용할 값을 바인딩한다.
네이티브 함수 호출
- 네이티브 SQL 함수를 호출하려면
cb.function(...)메서드를 사용한다.
Root<Member> m = cq.from(Member.class);
Expression<Long> function = cb.function("SUM", Long.class, m.get("age"));
cq.select(function);
동적 쿼리
- 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것을 동적 쿼리라 한다.
- 동적 쿼리는 문자 기반인 JPQL보다 코드 기반인 Criteria로 작성하는 것이 더 편리하다.
Integer age = 10;
String username = null;
String teamName = "teamA";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
Join<Member, join> t = m.join("team");
List<Predicate> criteria = new ArrayList<>();
if(age != null)
criteria.add(cb.equal(m.<Integer>get("age"), cb.parameter(Integer.class, "age")));
if(username != null)
criteria.add(cb.equal(m.get("username"), cb.parameter(String.class, "username")));
if(teamName != null)
criteria.add(cb.equal(t.get("name"), cb.parameter(String.class, "teamName")));
cq.where(cb.and(criteria.toArray(new Predicate[0])));
TypedQuery<Member> query = em.createQuery(cq);
if(age != null) query.setParameter("age", age);
if(username != null) query.setParamter("username", username);
if(teamName != null) query.setParamter("teamName", teamName);
List<Member> resultList = query.getResultList();
Criteria 메타 모델 API
- Criteria는 코드 기반이므로 컴파일 시점에 오류를 발견할 수 있다.
- 하지만
m.get("age")에서age는 문자이므로 오타가 발생해도 컴파일 시점에 발견하지 못한다. - 따라서 완전한 코드 기반이라 할 수 없다.
- 이런 부분까지 코드로 작성하려면 메타 모델 API를 사용하면 된다.
- 하지만
cq.select(m)
.where(cb.gt(m.get(Member_.age), 20))
.orderBy(cb.desc(m.get(Member_.age)));
m.get("age")가m.get(Member_.age)처럼 정적인 코드 기반으로 변경된 것을 볼 수 있다.Member_클래스를 메타 모델 클래스 라고 한다.
- 메타 모델 클래스는 코드 자동 생성기가 엔티티 클래스를 기반으로 만들어 준다.
JPAMetaModelEntityProcessor코드 생성기는 모든 엔티티 클래스를 찾아서엔티티명_모양의 메타 모델 클래스를 생성해준다.- 코드 생성기는 Maven 이나 Gradle 같은 빌드 도구를 사용해서 실행한다.
target/generated-sources/annotations하위에 메타 모델 클래스들이 생성된다.
QueryDSL
- Criteria는 코드 기반이므로 컴파일 단계에서 오류를 잡을 수 있다는 장점이 있지만, 코드가 너무 복잡하고 직관적으로 이해하기가 어렵다는 단점이 있다.
- 쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발할 수 있는 것이 QueryDSL 이다.
QueryDSL 설정
필요 라이브러리
querydsl-jpa: QueryDSL JPA 라이브러리querydsl-apt: 쿼리 타입을 생성할 때 필요한 라이브러리
환경 설정
- QueryDSL을 사용하려면 Criteria의 메타 모델처럼 엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성해야 한다.
JPAAnnotationProcessor코드 생성기가target/generated-sources위치에Q엔티티명의 쿼리 타입들을 생성한다.
시작
EntityManager em = emf.createEntityManager();
JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m");
List<Member> members = query.from(qMember)
.where(qMember.name.eq("member1"))
.orderBy(qMember.name.desc())
.list(qMember);
- QueryDSL을 사용하려면
JPAQuery객체를 생성해야 하는데, 이 때 엔티티 매니저를 생성자에 넘겨준다. - 사용할 쿼리 타입을 생성할 때는 생성자에 별칭을 주면 된다.
- 이 때 설정된 alias는 JPQL에서 별칭으로 사용한다.
기본 Q 생성
-
쿼리 타입은 사용하기 편리하도록 기본 인스턴스를 보관하고 있다.
public class QMember extends EntityPathBase<Member> { public static final QMember member = new QMember("member1"); ... }- 하지만 같은 엔티티를 조인하거나 같은 엔티티를 서브 쿼리에 사용하면 같은 별칭이 사용되므로 이때는 별칭을 직접 지정해서 사용해야 한다.
QMember qMember = new QMember("m"); // 직접 지정
QMember qMember = QMember.member; // 기본 인스턴스 사용
-
쿼리 타입의 기본 인스턴스를
import static을 활용해서 임포트하면 더 간결하게 사용할 수 있다.import static com.domain.jpa.QMember.member; public void basic() { ... JPAQuery query = new JPAQuery(em); List<Member> members = query.from(member) .where(member.name.eq("member1")) .orderBy(member.name.desc()) .list(member); }
검색 조건 쿼리
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<Item> list = query.from(item)
.where(item.name.eq("goods").and(item.price.gt(20000)))
.list(item);
-
where절에and나or를 사용할 수 있다.-
여러 검색 조건을 파라미터로 나열하여 넘겨주어도 된다. 이 때는
and연산이 된다..where(item.name.eq("goods"), item.price.gt(20000))
-
-
쿼리 타입의 필드는 필요한 대부분의 메소드를 명시적으로 제공한다.
item.price.between(10000, 20000); item.name.contains("item1"); // SQL에서 like '%item1%' 로 검색 item.name.startsWith("it") // SQL에서 like 'it%' 로 검색
결과 조회
- 쿼리 작성이 끝나고 결과 조회 메소드를 호출하면 실제 데이터베이스를 조회한다.
uniqueResult()나list()를 주로 사용하고 파라미터로 프로젝션 대상을 넘겨준다.uniqueResult(): 조회 결과가 한 건일 때 사용한다.- 조회 결과가 없으면
null을 반환하고, 결과가 두 개 이상이면NonUniqueResultException예외를 발생시킨다.
- 조회 결과가 없으면
singleResult():uniqueResult()와 같지만 결과가 두 개 이상이면 처음 데이터를 반환한다.list(): 결과가 두 개 이상일 때 사용한다.- 조회 결과가 없으면 빈 컬렉션을 반환한다.
페이징과 정렬
QItem item = QItem.item;
query.from(item)
.where(item.price.gt(20000))
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10)
.limit(20)
.list(item);
-
정렬은
orderBy()메서드를 사용하고, 쿼리 타입이 제공하는asc(),desc()를 사용한다. -
페이징은
offset()과limit()을 조합해서 사용한다. -
페이징은
restrict()메서드에QueryModifiers를 파라미터로 전달해 사용해도 된다.QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); // limit, offset List<Item> list = query.from(item) .restrict(queryModifiers) .list(item); -
실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야 한다.
- 이 때
list()대신listResults()를 사용할 수 있다.
SearchResults<Item> result = query.from(item) .where(item.price.gt(10000)) .offset(10) .limit(20) .listResults(item); long total = result.getTotal(); long limit = result.getLimit(); long offset = result.getOffset(); List<Item> results = result.getResults();listResults()메서드를 사용하면 전체 데이터 조회를 위한COUNT쿼리를 한 번 더 실행한다.- 전체 데이터 수는
getTotal()메서드로 반환받을 수 있다.
- 전체 데이터 수는
- 이 때
그룹
- 그룹은
groupBy()를 사용하고, 그룹화된 결과를 제한하려면having()을 사용한다.
query.from(item)
.groupBy(item.price)
.having(item.price.gt(1000))
.list(item);
조인
- 조인은
innerJoin,leftJoin,rightJoin,fullJoin을 사용할 수 있다.join(조인 대상, 별칭으로 사용할 쿼리 타입)
QOrder order = QOrder.order;
QMember member = QMember.member;
QOrderItem orderItem = QOrderItem.orderItem;
query.from(order)
.join(order.member, member)
.leftJoin(order.orderItems, orderItem)
.list(order);
- 조인에
on을 사용할 수도 있다.
query.from(order)
.leftJoin(order.orderItems, orderItem)
.on(orderItem.count.gt(2))
.list(order);
- 페치 조인을 사용할 수도 있다.
query.from(order)
.innerJoin(order.member, member).fetch()
.leftJoin(order.orderItems, orderItem).fetch()
.list(order);
from절에 여러 조인을 사용하는 세타 조인도 사용할 수 있다.
QOrder order = QOrder.order;
QMember member = QMember.member;
query.from(order, member)
.where(order.member.eq(member))
.list(order);
서브 쿼리
- 서브 쿼리는
JPASubQuery를 생성해서 사용한다.- 서브 쿼리 결과가 하나면
unique(), 여러건이면list()를 사용한다.
- 서브 쿼리 결과가 하나면
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");
query.from(item)
.where(item.price.eq(
new JPASubQuery().from(itemSub).unique(itemSub.price.max())
))
.list(item);
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");
query.from(item)
.where(item.in(
new JPASubQuery().from(itemSub)
.where(item.name.eq(itemSub.name))
.list(itemSub)
))
.list(item);
프로젝션과 결과 반환
select절에 조회 대상을 지정하는 것을 프로젝션이라 한다.
프로젝션 대상이 하나
- 프로젝션 대상이 하나면 해당 타입으로 반환한다.
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);
for(String name : result) {
System.out.println("name = " + name);
}
여러 컬럼 반환과 튜플
- 프로젝션 대상으로 여러 필드를 선택하면 QueryDSL은
Tuple이라는 내부 타입을 사용한다. - 조회 결과는
tuple.get()메소드에 조회한 쿼리 타입을 지정하여 사용한다.
QItem item = QItem.item;
List<Tuple> result = query.from(item).list(item.name, item.price);
for(Tuple tuple : result) {
System.out.println("name = " + tuple.get(item.name));
System.out.println("price = " + tuple.get(item.price));
}
빈 생성
- 쿼리 결과를 Entity가 아닌 사용자가 정의한 특정 객체로 받고 싶으면 빈 생성 기능을 사용한다.
- QueryDSL은 객체를 생성하는 다양한 방법을 제공한다.
- 프로퍼티 접근
- 필드 직접 접근
- 생성자 사용
public class ItemDTO {
private String username;
private int price;
public ItemDTO() {}
public ItemDTO(String username, int price) {
this.username = username;
this.price = price;
}
// getter, setter
...
}
프로퍼티 접근
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
Projections.bean()메서드는Setter를 사용해서 빈을 생성한다.- 쿼리 결과와 매핑할 프로퍼티 이름이 다르면
as(프로퍼티 이름)메서드를 사용한다.
필드 직접 접근
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
Projections.fields()메서드는 필드에 직접 접근해서 값을 채운다.- 필드를
private로 설정해도 동작한다.
- 필드를
생성자 사용
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.constructor(ItemDTO.class, item.name, item.price));
Projections.constructor()메서드는 생성자를 사용해서 빈을 생성한다.- 지정한 프로젝션과 파라미터 순서가 같은 생성자가 선언되어 있어야 한다.
DISTINCT
query.distinct().from(item)...
수정, 삭제 배치 쿼리
- QueryDSL도 수정, 삭제 같은 배치 쿼리를 지원한다.
수정 배치 쿼리
QItem item = QItem.item;
JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where(item.name.eq("item1"))
.set(item.price, item.price.add(100))
.execute();
삭제 배치 쿼리
QItem item = QItem.item;
JPADeleteClause deleteClause = new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("item1")).execute();
동적 쿼리
BooleanBuilder를 사용하면 특정 조건에 따른 동적 쿼리를 편리하게 생성할 수 있다.
SearchParam param = new SearchParam();
param.setName("item1");
param.setPrice(10000);
QItem item = QItem.item;
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.hasText(param.getName())) {
builder.and(item.name.contains(param.getName()));
}
if(param.getPrice() != null) {
builder.and(item.price.gt(param.getPrice()));
}
List<Item> result = query.from(item).where(builder).list(item);
메소드 위임
- Delegate methods 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있다.
- 메소드 위임 기능을 사용하려면 static 메서드를 만들고,
@QueryDelegate어노테이션에 속성으로 이 기능을 적용할 엔티티를 지정한다.
public class ItemExpression {
@QueryDelegate(Item.class)
public static BooleanExpression isExpensive(QItem item, Integer price) {
return item.price.gt(price);
}
}
-
static 메서드의 첫번째 파라미터는 대상 엔티티의 쿼리 타입을 지정하고, 그 다음 파라미터 부터는 필요한 파라미터를 정의한다.
-
생성된 쿼리 타입을 보면 해당 메서드가 추가된 것을 확인할 수 있다.
public class QItem extends EntityPathBase<Item> { ... public BooleanExpression isExpensive(Integer price) { return ItemExpression.isExpression(this, price); } }- 위에서 생성된 쿼리 타입의 메서드를 다음과 같이 사용할 수 있다.
query.from(item).where(item.isExpensive(30000)).list(item);