2.2. JDO 쿼리

Querydsl은 영속 도메인 모델에 대해 쿼리할 수 있는 범용적인 정적 타입 구문을 정의하고 있다. JDO와 JPA는 Querydsl이 지원하는 주요 기술이다. 이 안내 문서에서는 JDO와 함께 Querydsl을 사용하는 방법을 설명한다.

2.2.1. 메이븐 통합

메이븐 프로젝트의 의존 설정에 다음을 추가한다.

<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-jdo</artifactId>
  <version>${querydsl.version}</version>
</dependency>

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

다음으로 Querydsl에서 쿼리 타입을 생성하기 위해 사용하는 메이븐 APT 플러그인을 설정한다.

<project>
  <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.jdo.JDOAnnotationProcessor</processor>
            </configuration>
          </execution>
        </executions>
      </plugin>
    ...
    </plugins>
  </build>
</project>

JDOAnnotationProcessor는 javax.jdo.annotations.PersistenceCapable 애노테이션이 적용된 타입을 찾아서 그 타입을 위한 쿼리 타입을 생성한다.

`mvn clean install`을 실행하면, target/generated-sources/java 디렉토리에 Query 타입이 생성된다.

이클립스를 사용할 경우, `mvn eclipse:eclipse`을 실행하면 target/generated-sources/java 디렉토리가 소스 폴더에 추가된다.

생성된 Query 타입을 이용하면 JDO 쿼리 인스턴스와 쿼리 도메인 모델 인스턴스를 생성할 수 있다.

2.2.2. Ant 통합

클래스패스에 full-deps에 포함된 jar 파일들을 위치시키고, 다음 태스크를 이용해서 Querydsl 코드를 생성한다.

    <!-- APT based code generation -->
    <javac srcdir="${src}" classpathref="cp">
      <compilerarg value="-proc:only"/>
      <compilerarg value="-processor"/>
      <compilerarg value="com.mysema.query.apt.jdo.JDOAnnotationProcessor"/>
      <compilerarg value="-s"/>
      <compilerarg value="${generated}"/>
    </javac>

    <!-- compilation -->
    <javac classpathref="cp" destdir="${build}">
      <src path="${src}"/>
      <src path="${generated}"/>
    </javac>

src를 메인 소스 폴더로 변경하고, generated를 생성된 소스를 위한 폴더로 변경하고, build를 클래스 생성 폴더로 변경한다.

2.2.3. 쿼리 타입 사용하기

Querydsl을 이용해서 쿼리를 작성하려면, 변수와 Query 구현체를 생성해야 한다. 먼저 변수부터 시작해보자.

다음과 같은 도메인 타입이 있다고 가정하다.

@PersistenceCapable
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");

QCustomer는 원래 Customer 타입의 모든 프로퍼티를 public 필드로 반영한다. firstName 필다는 다음과 같이 접근할 수 있다.

customer.firstName;

2.2.4. 쿼리

JDOQuery가 JDO 모듈을 위한 Query 구현체이며, 다음과 같이 인스턴스를 생성한다.

PersistenceManager pm = ...;
JDOQuery query = new JDOQuery (pm);

firstName 프로퍼티가 Bob인 Customer를 조회하고 싶다면 다음의 쿼리를 사용하면 된다.

QCustomer customer = QCustomer.customer;
JDOQuery query = new JDOQuery (pm);
Customer bob = query.from(customer)
  .where(customer.firstName.eq("Bob"))
  .uniqueResult(customer);
query.close();

from 메서드는 쿼리 대상(소스)을 지정하고, where 부분은 필터를 정의하고, uniqueResult는 프로젝션을 정의해서 1개 결과만 리턴하라고 지시한다.

여러 소스로부터 쿼리를 만들고 싶다면 다음처럼 쿼리를 사용한다.

QCustomer customer = QCustomer.customer;
QCompany company = QCompany.company;
query.from(customer, company);

여러 필터를 사용하는 방법은 다음과 같다.

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

또는, 다음과 같이 해도 된다.

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

필터 조건을 or로 조합하고 싶다면 다음 패턴을 사용한다.

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

2.2.5. 일반 용법

JDOQuery 클래스의 cascading 메서드는 다음과 같다.

from: 쿼리 소스를 추가한다. 첫 번째 인자는 메인 소스가 되고, 나머지는 변수로 취급한다.

where: 쿼리 필터를 추가한다. 가변인자나 and/or 메서드를 이용해서 필터를 추가한다.

groupBy: 가변인자 형식의 인자를 기준으로 그룹을 추가한다.

having: Predicate 표현식을 이용해서 "group by" 그룹핑의 필터를 추가한다.

orderBy: 정렬 표현식을 이용해서 정렬 순서를 지정한다. 숫자나 문자열에 대해서는 asc()나 desc()를 사용하고, OrderSpecifier에 접근하기 위해 다른 비교 표현식을 사용한다.

limit, offset, restrict: 결과의 페이징을 설정한다. limit은 최대 결과 개수, offset은 결과의 시작 행, restrict는 limit과 offset을 함께 정의한다.

2.2.6. 정렬

정렬을 위한 구문은 다음과 같다.

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

2.2.7. 그룹핑

그룹핑은 다음과 같은 코드로 처리한다.

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

2.2.8. DeleteClause

Querydsl JDO에서 DeleteClause는 간단한 delete-where-execute 형태를 취한다. 다음은 몇 가지 예다.

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

JDODeleteClause 생성자의 두 번째 파라미터는 삭제할 엔티티 대상이다. where는 필요에 따라 추가할 수 있으며, execute를 실행하면 삭제를 수행하고 삭제된 엔티티의 개수를 리턴한다.

2.2.9. 서브쿼리

서브쿼리를 만들려면 JDOSubQuery를 사용하면 된다. 서브쿼리를 만들기 위해 from 메서드로 쿼리 파라미터를 정의하고, unique나 list를 이용한다. unique는 단일 결과를 위해 사용하고 list는 리스트 결과를 위해 사용한다. 서브쿼리도 쿼리처럼 타입에 안전한 Querydsl 표현식이다.

QDepartment department = QDepartment.department;
QDepartment d = new QDepartment("d");
query.from(department)
    .where(department.employees.size().eq(
        new JDOSubQuery().from(d).unique(AggregationFunctions.max(d.employees.size()))
     )).list(department);

위 코드는 다음의 네이티브 JDO 쿼리를 표현한다.

SELECT this FROM com.mysema.query.jdoql.models.company.Department
WHERE this.employees.size() ==
(SELECT max(d.employees.size()) FROM com.mysema.query.jdoql.models.company.Department d)

다른 예제

QEmployee employee = QEmployee.employee;
QEmployee e = new QEmployee("e");
query.from(employee)
    .where(employee.weeklyhours.gt(
        new JDOSubQuery().from(employee.department.employees, e)
        .where(e.manager.eq(employee.manager))
        .unique(AggregationFunctions.avg(e.weeklyhours))
    )).list(employee);

위 코드는 다음의 네이티브 JDO 쿼리를 표현한다.

SELECT this FROM com.mysema.query.jdoql.models.company.Employee
WHERE this.weeklyhours >
(SELECT avg(e.weeklyhours) FROM this.department.employees e WHERE e.manager == this.manager)

2.2.10. 네티이브 SQL 사용하기

JDOSQLQuery 클래스를 사용하면 JDO의 네이티브 SQL을 Querydsl에서 사용할 수 있다.

이걸 사용하려면 SQL 스키마를 위한 Querydsl 쿼라 티입을 생성해야 한다. 다음은 이를 위한 Maven 설정 예를 보여주고 있다.

<project>
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-maven-plugin</artifactId>
        <version>${querydsl.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>
      ...
    </plugins>
  </build>
</project>

지정한 위치에 쿼리 타입을 성공적으로 생성했다면, 쿼리에서 그 타입을 사용할 수 있다.

한 개 컬럼 쿼리:

// 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");

JDOSQLQuery query = new JDOSQLQuery(pm, templates);
List<String> names = query.from(cat).list(cat.name);

다중 컬럼 쿼리:

query = new JDOSQLQuery(pm, templates);
List<Object[]> rows = query.from(cat).list(cat.id, cat.name);

모든 컬럼 쿼리:

List<Object[]> rows = query.from(cat).list(cat.all());
 

조인을 이용한 쿼리:

query = new JDOSQLQuery(pm, 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 JDOSQLQuery(pm, templates);
List<CatDTO> catDTOs = query.from(cat)
    .orderBy(cat.name.asc())
    .list(ConstructorExpression.create(CatDTO.class, cat.id, cat.name));