2.8. Scala에서 쿼리하기

querydsl-scala 모듈을 통해 Scala에서 Querydsl을 사용할 수 있다. 이 모듈을 사용하려면 메이븐 빌드에 다음의 코드를 추가한다.

<dependency>
  <groupId>com.mysema.querydsl</groupId>
  <artifactId>querydsl-scala</artifactId>
  <version>${querydsl.version}</version>
</dependency>

2.8.1. Scala를 위한 DSL 표현

Scala 용 Querydsl은 표현식 생성을 위한 별도 DSL을 제공한다. Scala DSL은 가독성과 간결함을 향상시키기 위해 연산자 오버로딩, 함수 포인터, 임의 임포트 등 언어의 특징을 활용한다.

주요 대체 DSL은 다음과 같다.

//Standard              Alternative

expr isNotNull          expr is not(null)
expr isNull             expr is null
expr eq "Ben"           expr === "Ben"
expr ne "Ben"           expr !== "Ben"
expr append "X"         expr + "X"
expr isEmpty            expr is empty
expr isNotEmpty         expr not empty

// boolean
left and right          left && right
left or right           left || right
expr not                !expr

// comparison
expr lt 5               expr < 5
expr loe 5              expr <= 5
expr gt 5               expr > 5
expr goe 5              expr >= 5
expr notBetween(2,6)    expr not between (2,6)
expr negate             -expr

// numeric
expr add 3              expr + 3
expr subtract 3         expr - 3
expr divide 3           expr / 3
expr multiply 3         expr * 3
expr mod 5              expr % 5

// collection
list.get(0)             list(0)
map.get("X")            map("X")

2.8.2. 향상된 프로젝션

Querydsl Scala 모듈은 Querydsl의 쿼리 프로젝션을 Scala에 더욱 알맞게 만들기 위해 몇몇 임의 변환을 제공한다.

Querydsl 쿼리에서 Scala 프로젝션을 활성화하려면 RichProjectableRichSimpleProjectable 래퍼를 사용해야 한다. com.mysema.query.scala.Helpers를 임포트 함으로써 필요한 임의 변환이 가능해진다.

예를 들어, 표준 API를 이용한 다음 쿼리는 Object[] 타입의 java.util.List를 리턴한다.

query.from(person).list(person.firstName, person.lastName, person.age)

임의 변환을 추가함으로써, list대신에 select를 이용해서 Scala List 타입 결과를 사용할 수 있다. 또한, uniqueResult나 singleResult 대신에 unique와 single를 이용해서 Option 타입으로 결과를 사용할 수 있다.

임의 변환을 사용하면 앞서 쿼리를 다음과 같이 작성할 수 있다.

import com.mysema.query.scala.Helpers._

query.from(person).select(person.firstName, person.lastName, person.age)

이 경우 결과 타입은 List[(String,String,Integer)] 즉, Tuple3[String,String,Integer]의 List가 된다.

2.8.3. SQL을 이용한 쿼리

자바를 위한 Querydsl SQL과 마찬가지로, 쿼리를 만들려면 쿼리 타입을 생성해야 한다. 다음은 쿼리 타입 생성을 위한 코드 예제다.

빈 타입 없이 생성하기:

val directory = new java.io.File("target/jdbcgen1")
val namingStrategy = new DefaultNamingStrategy()
val exporter = new MetaDataExporter()
exporter.setNamePrefix("Q")
exporter.setPackageName("com.mysema")
exporter.setSchemaPattern("PUBLIC")
exporter.setTargetFolder(directory)
exporter.setSerializerClass(classOf[ScalaMetaDataSerializer])
exporter.setCreateScalaSources(true)
exporter.setTypeMappings(ScalaTypeMappings.create)
exporter.export(connection.getMetaData)

빈 타입을 이용해서 생성하기:

val directory = new java.io.File("target/jdbcgen2")
val namingStrategy = new DefaultNamingStrategy()
val exporter = new MetaDataExporter()
exporter.setNamePrefix("Q")
exporter.setPackageName("com.mysema")
exporter.setSchemaPattern("PUBLIC")
exporter.setTargetFolder(directory)
exporter.setSerializerClass(classOf[ScalaMetaDataSerializer])
exporter.setBeanSerializerClass(classOf[ScalaBeanSerializer])
exporter.setCreateScalaSources(true)
exporter.setTypeMappings(ScalaTypeMappings.create)
exporter.export(connection.getMetaData)

2.8.3.1. 컴팩트 쿼리

Querydsl Scala은 Querydls SQL을 위한 컴팩트 쿼리를 제공한다. 이 구문은 Rogue 프레임워크의 도메인 지향 쿼리 구문에서 영감을 얻었다.

RelationalPath 인스턴스를 쿼리로 임의 변환해서 도메인 지향 쿼리를 구현한다. 서비스나 DAO 클래스가 com.mysema.query.scala.sql.SQLHelpers 트레잇을 상속하면 이 기능을 사용할 수 있다.

컴팩트 쿼리를 사용하면, 쿼리의 시작 지점으로 메타 모델 클래스를 사용할 수 있다.

다음의 일반 쿼리를 사용하는 대신에

query().from(employee).select(employee.firstName, employee.lastName)

다음과 같이 Employee 또는 QEmployee의 companion 객체를 사용할 수 있다.

Employee.select(_.firstName, _.lastName)

표현식에 orderBy, where, select, single, unique를 사용하는 대신, 쿼리의 루트 표현식을 파라미터로 받고 다른 표현식을 리턴하는 함수를 사용할 수 있다. 다음은 앞 예제를 확장한 형식이다.

Employee.select({ e => e.firstName }, { e => e.lastName })

자세한 내용은 com.mysema.query.scala.sql.RichSimpleQuery의 시그너처를 참고하기 바란다.

2.8.3.2. 코드 생성

querydsl-maven-plugin을 이용해서 SQL 메타타입과 프로젝션을 위한 Scala 소스를 생성한다. 다음은 설정 예다.

<project>
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-maven-plugin</artifactId>
        <version>${querydsl.version}</version>
        <configuration>
          <jdbcDriver>com.mysql.jdbc.Driver</jdbcDriver>
          <jdbcUrl>jdbc:mysql://localhost:3306/test</jdbcUrl>
          <jdbcUser>matko</jdbcUser>
          <jdbcPassword>matko</jdbcPassword>
          <packageName>com.example.schema</packageName>
          <targetFolder>${project.basedir}/src/main/scala</targetFolder>
          <exportBeans>true</exportBeans>
          <createScalaSources>true</createScalaSources>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.16</version>
          </dependency>
          <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-scala</artifactId>
            <version>${querydsl.version}</version>
          </dependency>
          <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
          </dependency>
        </dependencies>
      </plugin>
      ...
    </plugins>
  </build>
</project>

querydsl:export 메이븐 골을 실행한다.

2.8.4. 다른 백엔드에 대한 쿼리

다른 백엔드에 대해 쿼리를 하려면, Expression 모델을 수동으로 만들거나 별칭 함수를 사용해야 한다.

다음은 JPA를 이용할 때의 예제다.

@Entity
class User {
  @BeanProperty
  @Id
  var id: Integer = _;
  @BeanProperty
  var userName: String = _;
  @BeanProperty
  @ManyToOne
  var department: Department = _;
}

@Entity
class Department {
  @BeanProperty
  @Id
  var id: Integer = _;
  @BeanProperty
  var name: String = _;
}

다음은 몇 가지 쿼리 예제다.

List

val person = Person as "person"

query.from(person).where(person.firstName like "Rob%").list(person)

Unique result

query.from(person).where(person.firstName like "Rob%").unique(person)

Long where

query.from(person)
  .where(person.firstName like "Rob%", person.lastName like "An%")
  .list(person)

Order

query.from(person).orderBy(person.firstName asc).list(person)

Not null

query.from(person)
  .where(person.firstName isEmpty, person.lastName isNotNull)
  .list(person)

쿼리 생성을 위한 팩토리 메서드

def query() = new JPAQuery(entityManager)

위 쿼리를 실행하려면 다음과 같이 변수를 생성해야 한다.

val person = Person as "person"

하이버네이트에서 XML 기반 설정을 사용할 경우, 아직 Scala 모듈을 사용할 수 없다. HibernateDomainExporter는 현재 자바 소스 파일 생성만 지원한다.