객체지향 쿼리 언어(2)

Jeongyeon Kim·2023년 2월 5일
0

JPA

목록 보기
10/11
post-thumbnail

3. Criteria

Criteria 기초

  • Criteria API는 javax.persistence.criteria 패키지에 있음
// JPQL
// select m from Member m
// where m.username='회원1'
//order by m.age desc

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Member> cq = cb.createQuery(Member.class);

Root<Member> m = cq.from(Member.class);

// 검색 조건 정의
Predicate usernameEqual = cb.equal(m.get("username"), "회원1");

// 정렬 조건 정의
javax.persistence.criteria.Order ageDesc = cb.desc(m.get("age"));

// 쿼리 생성
cq.select(m)
	.where(usernameEqual)
    .orderBy(ageDesc);
    
List<Member> resultList = em.createQuery(cq).getResultList();
  • 쿼리 루트
    • Root< Member > m = cq.from(Member.class); 여기서 m이 쿼리 루트
    • 쿼리 루트는 조회의 시작점
    • Criteria에서 사용되는 특별한 별칭
    • 별칭은 엔티티에만 부여 가능

Criteria 쿼리 생성

  • CriteriaBuilder.createQuery() 메소드로 Criteria 쿼리 생성
  • Criteria 쿼리 생성 시 파라미터로 쿼리 결과에 대한 반환 타입 지정 가능
  • 반환 타입을 지정할 수 없거나 반환 타입이 둘 이상이면 Object(Object[])로 반환 받음
  • 튜플로도 반환 받을 수 있음

조회

  • 조회 대상을 한 건, 여러 건 지정
    • select: 조회 대상 한 건
    • multiselect: 조회 대상 여러 건
  • DISTINCT
    • select, multiselect 다음에 distinct(true) 사용
  • NEW, construct()
    • cb.construct(클래스 타입, ...)
  • 튜플
    • 이름 기반이므로 순서 기반의 Object[] 보다 안전
    • tuple.getElements() 같은 메소드를 사용해서 현재 튜플의 별칭과 자바 타입 조회 가능
    • 튜플 사용할 때는 별칭 필수

집합

  • groupBy
  • having

정렬

  • orderBy
  • cb.desc(...), cb.asc(...)

조인

  • join()
  • JoinType 클래스
  • fetch(조인대상, JoinType)

서브 쿼리

/* JPQL
	select m from Member m
    where exists
    	(select t from m.team t where t.name='팀A')
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);

// 서브 쿼리에서 사용되는 메인 쿼리의 m
Root<Member> m = mainQuery.from(Member.class);

// 서브 쿼리 생성
Subquery<Team> subQuery = mainQuery.subquery(Team.class);
Root<Member> subM = subQeury.correlate(m);
Join<Member, Team> t = sbuM.join("team");
subQuery.select(t)
	.where(cb.equal(t.get("name"), "팀A"));

// 메인 쿼리 생성
mainQuery.select(m)
	.where(cb.exists(subQuery));
    
List<Member> resultList = em.createQuery(mainQuery).getResultList();

IN 식

  • in(...)

CASE 식

  • selectCase()
  • when()
  • otherwise()

파라미터 정의

...
cq.select(m)
	.where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam")));

List<Member> resultList = em.createQuery(cq)
	.setParameter("usernameParam", "회원1")
    .getResultList();

네이티브 함수 호출

  • cb.function(...)
  • 하이버네이트 구현체는 방언에 사용자정의 SQL 함수를 등록해야 호출할 수 있음

동적 쿼리

  • 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것을 동적 쿼리라 함
// 검색 조건
Integer age = 10;
String username = null;
String teamName = "팀A";

// Criteria 동적 쿼리 생성
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);

Root<Member> m = cq.from(Member.class);
Join<Member, Team> t = m.join("team");

List<Predicate> criteria = new ArrayList<Predicate>();

if (age != null) criteria.add(cb.equal(m.<Integer>get("age"), 
	cb.parameter(Integer.class, "age")));
if (username != null) criteri.add(cb.equal(m.get("username"),
	cb.paramter(String.class, "username")));
if (teamName != null) criteria.add(cb.equal(t.get("name"),
	cb.paramter(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.setParameter("username", username);
if (teamName ! null) query.setParameter("teamName", teamName);

List<Member> resultList = query.getResultList();

Criteria 메타 모델 API

  • m.get("age")에서 age는 문자인데 실수로 잘못 적어도 컴파일 시점에 에러 발견 불가
    ➡️ 메타 모델 API 사용
  • 엔티티 -> 코드 자동 생성기 -> 메타 모델 클래스

2. QueryDSL

QueryDSL 설정

<dependency>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
  <version>3.6.3</version>
</dependency>

<dependency>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-apt</artifactId>
  <version>3.6.3</version>
  <scope>provided</scope>
</dependency>
  • querydsl-jpa: QueryDSL JPA 라이브러리
  • querydsl-apt: 쿼리 타입(Q) 생성할 때 필요한 라이브러리
<build>
  <plugins>
    <plugin>
      <groupId>com.mysema.maven</groupId>
  	  <artifactId>apt-maven-plugin</artifactId>
  	  <version>1.1.3</version>
      <executions>
        <execution>
          <goals>
            <goal>process</goal>
          </goals>
          <configuration>
            <outputDirectory>target/generated-sources/java</outputDirectory>
            <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

시작

import static jpabook.jpashop.domain.QMember.member

public void basic() {
	EntityManager em = emf.createEntityManager();
    
    JPAQuery query = new JPAQuery(em);
    List<Member> members = query.from(member)
    						.where(member.name.eq("회원1"))
                            .orderBy(member.name.desc())
                            .list(member);

검색 조건 쿼리

  • QueryDSL의 where 절에는 and나 or, between, contains, startsWith 사용 가능

결과 조회

  • uniqueResult(): 조회 결과가 한 건일 때 사용, 조회 결과가 없으면 null을 반환하고 하나 이상이면 com.mysema.query.NonUniqueResultException 예외 발생
  • singleResult(): uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환
  • list(): 결과가 하나 이상일 대 사용, 결과가 없으면 빈 컬렉션 반환

페이징과 정렬

  • 정렬은 orderBy를 사용하는데 쿼리 타입(Q)이 제공하는 asc(), desc() 사용
  • 페이징은 offset과 limit 조합해서 사용
  • 페이징은 restrict() 메소드에 com.mysema.query.QueryModifiers를 파라미터로 사용해도 됨
  • 실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야 하기 때문에 listResults() 사용
    ➡️ 전체 데이터 조회를 위한 count 쿼리를 한 번 더 실행하고 SearchResults를 반환하는데 이 객체에서 전체 데이터 수를 조회할 수 있음

그룹

  • groupBy
  • having

조인

  • innerJoin, join
  • leftJoin, rightJoin, fullJoin
  • fetch

서브 쿼리

  • com.mysema.query.jpa.JPASubQuery 생성해서 사용
  • 서브 쿼리의 결과가 하나면 unique(), 여러 건이면 list() 사용

프로젝션과 결과 반환

  • 프로젝션 대상으로 여러 필드 선택하면 com.mysema.query.Tuple 사용
  • 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶으면 빈 생성 기능 사용
    • 프로퍼티 접근: Projections.bean()
    • 필드 직접 접근: Projections.fields()
    • 생성자 사용: Projections.constructor()
  • distinct()

수정, 삭제 배치 쿼리

  • QueryDSL도 JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리함
  • 수정 배치 쿼리는 com.mysema.query.jpa.impl.JPAUpdateClause 사용
  • 삭제 배치 쿼리는 com.mysema.query.jpa.impl.JPADeleteClause 사용

동적 쿼리

  • com.mysema.query.BooleanBuilder

메소드 위임

  • 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있음
public class ItemExpression {

	@QueryDelegate(Item.class)
    public static BooleanExpression isExpensive(QItem item, Integer price) {
    	return item.price.gt(price);
    }
}
// 쿼리 타입에 생성된 결과
public class QItem extends EntityPathBase<Item> {
	...
    public com.mysema.query.types.expr.BooleanExpression isExpensive(Integer price) {
    	return ItemExpression.isExpensive(this, price);
    }
}
profile
Backend Developer👩🏻‍💻

0개의 댓글