[QueryDSL] 2.2 Querying JPA

http://source.mysema.com/static/querydsl/2.1.1/reference/html/ch02s02.html

2.2 JPA 쿼리

QueryDSL은 영속화 하려는 도메인 모델 데이터를 기반으로 타입 안전한 일관된 문법을 제공한다. 이번 가이드에서는 QueryDSL을 JPA/Hibernate와 같이 사용하는 방법을 살펴본다.

2.2.1. 메이븐

다음 의존성 설정을 메이븐 프로젝트에 추가한다. Mysema 메이븐 저장소 URL은 http://source.mysema.com/maven2/releases.

<dependency>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-apt</artifactId>
  <version>${querydsl.version}</version>
  <scope>provided</scope>
</dependency>   
   
<dependency>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
  <version>${querydsl.version}</version>
</dependency>

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.1</version>
</dependency> 

메이븐 APT 플러그인 설정 추가 한다.

<project>
  <build>
  <plugins>
    …
    <plugin>
      <groupId>com.mysema.maven</groupId>
      <artifactId>maven-apt-plugin</artifactId>
      <version>1.0</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>
</project>

JPAAnnotationProcessor가 javax.persistence.Entity 애노테이셔니이 붙어있는 도메인 타입을 찾아서 쿼리 타입을 생성해 준다.

하이버네이트 애노테이션을 사용하면 APT 프로세서로 com.mysema.query.apt.hibernate.HibernateAnnotationProcessor를 사용해야 한다.

이클립스를 사용하고 있다면 mvn eclipse:eclipse를 실행해서 target/generated-source.java를 소스 폴더로 사용하도록 이클립스 프로젝틀 갱신하라.

이제 JPQL 쿼리 인스턴스와 쿼리 도메인 모델 인스턴스를 만들 수 있다.

2.2.2. 앤트 연동

생략

2.2.3. hbm.xml 파일에서 모델 생성하기

XML 기반 설정으로 하이버네이트를 사용하고 있다면, XML 메타데이터를 사용해서 QueryDSL 모델을 만들 수도 있다.

com.mysema.qeury.jpa.hiberante.HibernateDoaminExporter는 다음과 같은 기능을 제공한다.

HibernateDomainExporter exporter = new HibernateDomainExporter(
  "Q",                     // name prefix
  new File("target/gen3"), // target folder
  configuration);          // instance of org.hibernate.cfg.Configuration

exporter.export();

HibernateDomainExporter는 리플렉션을 사용하기 때문에 도메인 타입을 참조할 수 있는 클래스패스에서 실행해야 한다.

2.2.4. 쿼리 타입 사용하기

QueryDSL로 쿼리를 만들려면 변수와 쿼리 구현체의 인스턴스를 만들어야 한다. 변수부터 시작하겠다.

프로젝트에 다음과 같은 도메인 타입이 있다고 가정해보자.

@Entity
public class Customer {
    private String firstName;
    private String lastName;

    public String getFirstName(){
        return firstName;
    }

    public String getLastName(){
        return lastName;
    }

    public void setFirstName(String fn){
        firstName = fn;
    }

    public void setLastName(String ln)[
        lastName = ln;
    }
}

Querydsl은 Customer와 같은 패키지에 QCustomer라는 이름으로 쿼리 타입을 생성해준다. Querydsl 쿼리에서 Customer 타입을 대신할 정적 타입 변수로 QCustomer를 사용할 수 있다.

QCustomer는 정적 필드로 접근할 수 있는 기본 인스턴스 변수를 제공한다.

QCustomer customer = QCustomer.customer;

이 방법 말고, 다음과 같이 Customer 변수를 만들 수도 있다.

QCustomer customer = new QCustomer("myCustomer");

2.2.5. 쿼리

Querydsl JPA 모듈은 JPA와 하이버네이트 API 둘 다 지원한다.

하이버네이트 API를 사용하려면 다음과 같이 HibernateQuery 인스턴스를 사용하면 된다.

// where session is a Hibernate session
JPQLQuery query = new HibernateQuery (session);

JPA API를 사용하려면 다음과 같이 JPAQuery 인스턴스를 만들면 된다.

// where entityManager is a JPA EntityManager  
JPQLQuery query = new JPAQuery (entityManager);

HibernateQuery와 JPAQuery 둘 다 JPQLQuery 인터페이스 구현체다.

JPA 구현체로 하이버네이트를 사용할 때는 JPAQeury 기본 설정을 사용하면 되고, EclipseLink를 JPA 구현체로 사용할 때는 다음과 같이 JPAQuery 인스턴스를 생성한다.

// where entityManager is a JPA EntityManager  
JPQLQuery query = new JPAQuery (entityManager, EclipseLinkTemplates.DEFAULT);

Bob이라는 이름을 가지고 있는 customer를 가져오는 쿼리는 다음과 같다.

QCustomer customer = QCustomer.customer;
JPQLQuery query = new HibernateQuery (session);
Customer bob = query.from(customer)
  .where(customer.firstName.eq("Bob"))
  .uniqueResult(customer);

from 호출은 쿼리 소스를 정의하며, where 절은 필터를 정의하고 uniqueResult는 프로젝션을 정의하고 Querydsl이 단일 엘리먼트를 반환하도록 알려준다. 참. 쉽죠?

여러 소스를 기반으로 쿼리를 만들 때는 다음과 같이 한다.

query.from(customer, company);

여러 필터를 사용할 때는 다음과 같다.

query.from(customer)
    .where(customer.firstName.eq("Bob"), customer.lastName.eq("Wilson"));  

또는 다음과 같이 사용한다.

query.form(customer)
    .where(customer.firstName.eq("Bob").and(customer.lastName.eq("Wilson")));

네이티브 JPQL 쿼리로는 다음과 같이 작성할 수 있겠다.

from Customer as customer
    where customer.firstName = "Bob" and customer.lastName = "Wilson"

2.2.6. 조인 사용하기

Querydsl은 JPQL로 다음과 같은 조인을 제공한다. inner join, join, left join, full join. 조인을 사용할 때도 타입 안전성을 보장할 수 있으며 다음과 같이 사용한다.

query.from(cat)
    .innerJoin(cat.mate, mate)
    .leftJoin(cat.kittens, kitten)
    .list(cat);

네이티브 JPQL로는 다음과 같이 표현할 수 있다.

from Cat as cat
    inner join cat.mate as mate
    left outer join cat.kittens as kitten

또 다른 예제를 보자.,

query.from(cat)
    .leftJoin(cat.kittens, kitten)
    .on(kitten.bodyWeight.gt(10.0))
    .list(cat);

JPQL로는 다음과 같다.

from Cat as cat
    left join cat.kittens as kitten
    with kitten.bodyWeight > 10.0

2.2.7. 사용법

JPQLQuery 인터페이스는 다음과 같다.

from: 쿼리 소스를 정의한다.

innerJoin, join, leftJoin, fullJoin, on: 조인할 것을 정의한다. 조인 메서드의 첫번째 인자는 조인할 소스이고 두번째는 타겟(alias)이다.

where: 쿼리 필터를 정의한다. 가변 인자 또는 and 연산자로 이어 붙일 수 있다.

groupBy: 가변인자 형식으로 묶음에 사용할 인자를 나열한다

having: “group by” 의 having filter를 정의한다. Predicate expression의 가변 인자를 사용한다.

orderBy: Order expression 가변 인자를 사용해서 정렬 방식을 정의한다. asc()와 desc()를 숫자, 문자 또는 OrderSpecifier 인스턴스에 접근할 수 있는 어떠한 비교 가능한 표현식에 적용한다.

limit, offset, restrict: 페이징 할 때 사용한다. limit로 최대한 가져올 행 수를 설정하고, offset으로 스킵할 행 수를 정하거나, restrict로 둘 다 한방에 호출할 수 있다.

2.2.8. 정렬

정렬하는 방법은 다음과 같다.

query.from(customer)
    .orderBy(customer.lastName.asc(), customer.firstName.desc())
    .list(customer);

JPQL로는 다음과 같다.

from Customer as customer
    order by customer.lastName asc, customer.firstName desc

2.2.9. Grouping

grouping 방법은 다음과 같다.

query.from(customer)
    .groupBy(customer.lastName)
    .list(customer.lastName);

JPQL로는 다음과 같다.

select customer.lastName
    from Customer as customer
    group by customer.lastName

2.2.10. Delete 절

Querydsl에서 Delete 절은 단순하게 delete-where-execute 형식이다. 예제는 다음과 같다.

QCustomer customer = QCustomer.customer;
// delete all customers
new HibernateDeleteClause(session, customer).execute();
// delete all customers with a level less than 3
new HibernateDeleteClause(session, customer).where(customer.level.lt(3)).execute();

HibernateDeleteClause의 두번째 매개변수가 지워질 엔티티다. where 호출은 부가적이며 execute를 실행하면 삭제를 수행하고 삭제된 엔티티 갯수를 반환한다.

JPA API로 Delete할 때는 JPADeleteClause를 사용한다.

2.2.11. Update 절

Querydsl에서 Update 절은 update-set/where-execute 형식이다. 예제는 다음과 같다.

QCustomer customer = QCustomer.customer;
// rename customers named Bob to Bobby
new HibernateUpdateClause(session, customer).where(customer.name.eq("Bob"))
    .set(customer.name, "Bobby")
    .execute();

HibernateUpdateClause의 두번째 매개변수가 수정될 엔티티다. set 호출은 SQL Update 스타일로 수정할 프로퍼티를 지정하고, call은 갱신을 실행하고 갱신된 엔티티 갯수를 반환한다.

JPA API로 Update할 때는 JPAUpdateClause를 사용한다.

2.2.12. 서브쿼리

HibernateSubQuery 인스턴스를 만들어서 서브쿼리를 만들 수 있다. from으로 쿼리 매개변수를 정의하고, unique나 list를 사용해서 서브쿼리를 만든다.

query.from(department)
    .where(department.employees.size().eq(
        new HibernateSubQuery().from(d).unique(d.employees.size().max())
     )).list(department);

또 다른 예제를 살펴보자.

query.from(employee)
    .where(employee.weeklyhours.gt(
        new HibernateSubQuery().from(employee.department.employees, e)

        .where(e.manager.eq(employee.manager))
        .unique(e.weeklyhours.avg())
    )).list(employee);

JPA 기반 서브 쿼리를 사용할 때는 JPASubQuery를 사용한다.

2.2.13. 오리지널 쿼리

쿼리를 실행하기 전에 오리지널 쿼리를 조작할 필요가 있다면 다음과 같이 오리지널 쿼리를 노출시킬 수 있다.

HibernateQuery query = new HibernateQuery(session);
org.hibernate.Query hibQuery = query.from(employee).createQuery(employee);
hibQuery.setResultTransformer(someTransformer);
List results = hibQuery.list();

2.2.14. 하이버네이트 쿼리에서 네이티브 쿼리 사용하기

Querydsl은 하이버네이트에서 네이티브 쿼리를 사용할 수 있도록 지원한다.

그러려면 먼저 SQL 스키마에 대한 Querydsl 쿼리 타입을 만들어야 된다. 다음과 같은 메이븐 설정을 사용할 수 있다.

<plugin>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-maven-plugin</artifactId>
  <version>${project.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>export</goal>
      </goals>
    </execution>           
  </executions>
  <configuration>
    <jdbcDriver>org.apache.derby.jdbc.EmbeddedDriver</jdbcDriver>
    <jdbcUrl>jdbc:derby:target/demoDB;create=true</jdbcUrl>
    <packageName>com.mycompany.mydomain</packageName>
    <targetFolder>${project.basedir}/target/generated-sources/java</targetFolder>
  </configuration>
  <dependencies>
    <dependency>
      <groupId>org.apache.derby</groupId>
      <artifactId>derby</artifactId>
      <version>${derby.version}</version>
    </dependency>
  </dependencies>
</plugin>

쿼리 타입을 성공적으로 만들어지면 쿼리에서 다음과 같이 사용할 수 있다.

컬럼 한개 조회

// serialization templates
SQLTemplates templates = new DerbyTemplates();
// query types (S* for SQL, Q* for domain types)
SAnimal cat = new SAnimal("cat");  
SAnimal mate = new SAnimal("mate");
QCat catEntity = QCat.cat;         
 
HibernateSQLQuery query = new HibernateSQLQuery(session, templates);
List<String> names = query.from(cat).list(cat.name);

컬럼 여러개 조회

query = new HibernateSQLQuery(session, templates); List rows = query.from(cat).list(cat.id, cat.name);

모든 컬럼 조회

List rows = query.from(cat).list(cat.all());

쿼리는 SQL로 하고 엔티티로 받기

query = new HibernateSQLQuery(session, templates);
List<Cat> cats = query.from(cat).orderBy(cat.name.asc()).list(catEntity);

조인을 사용한 조회

query = new HibernateSQLQuery(session, templates);
cats = query.from(cat)
    .innerJoin(mate).on(cat.mateId.eq(mate.id))
    .where(cat.dtype.eq("Cat"), mate.dtype.eq("Cat"))
    .list(catEntity);

쿼리하고 DTO로 받기

query = new HibernateSQLQuery(session, templates);
List<CatDTO> catDTOs = query.from(cat)
    .orderBy(cat.name.asc())
    .list(EConstructor.create(CatDTO.class, cat.id, cat.name));

JPA API를 사용할 때는 HibernateSQLQuery 대신 JPASQLQeury를 사용하면 된다.

[QueryDSL] 2.1.1 Chapter 1. Introduction

http://source.mysema.com/static/querydsl/2.1.1/reference/html/ch01.html

1. 소개

1-1. 배경

문자열 연결해서 쿼리 만들면 코드 읽기가 어렵다. 타입 안전성을 보장하지도 못한다.

도메인 모델을 타입 안정한 방법으로 변경할 수 있다는 것은 소프트웨어 개발에 큰 장점이다.

QueryDSL은 처음에 HQL을 타겟으로 작업했지만, 요즘은 콜렉션, JDO, JDBC, Lucene, Hibernate Search, MongoDB, RDFBean 등을 지원한다.

1-2. 원칙

타입 안전성(Type safety)가 QueryDSL의 핵심 원칙이다. 도메인 타입을 통해 생성된 쿼리 타입과 속성을 사용하고, 메서드 호출 역시 모두 타입 안전한 방식으로 처리한다.

일관성(Consistency) 역시 중요한 원칙이다. 공통의 쿼리 인터페이스를 기반으로 여러 구현체를 제공한다.

모든 쿼리 인스턴스를 계속해서 재사용할 수 있다.

주요 API

http://source.mysema.com/static/querydsl/2.1.1/apidocs/com/mysema/query/Query.html

Query defines the main query interface of the fluent query language.

Note that the from method has been left out, since there are implementation specific variants of it.

http://source.mysema.com/static/querydsl/2.1.1/apidocs/com/mysema/query/Projectable.html

Projectable defines default projection methods for Query implementations. All Querydsl query implementations should implement either this interface or SimpleProjectable.

http://source.mysema.com/static/querydsl/2.1.1/apidocs/com/mysema/query/types/Expression.html

Expression defines a general typed expression in a Query instance. The generic type parameter is a reference to the type the expression is bound to.