Gradle と Querydsl Scala を使った Querydsl SQL のコード生成

前回JPA に続き、今回は Gradle と Querydsl Scala を使って Querydsl SQL のコード生成を試します。

ソースは http://github.com/fits/try_samples/tree/master/blog/20150810/

はじめに

Querydsl SQL の場合は ScalaJava と同じ要領でコードを生成します。 (Java の場合は 「Gradle を使った Querydsl SQL のコード生成」 参照)

ただし、MetaDataExporterScala 用の serializerClass 等を設定する必要があります。

Gradle ビルド定義

Gradle 用のビルド定義ファイルは以下のようになります。

build.gradle
apply plugin: 'scala'

// Querydsl のソース生成先パッケージ名
ext.modelPackage = 'sample.model'
// Querydsl のソース生成先ディレクトリ
ext.qdslDestDir = 'src/main/qdsl-generated'
// DB接続 URL
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.querydsl:querydsl-sql-codegen:4.0.3'
        classpath 'com.querydsl:querydsl-scala:4.0.3'
        classpath 'org.scala-lang:scala-library:2.11.7'
        // MySQL へ接続してコード生成する場合
        classpath 'mysql:mysql-connector-java:5.1.36'
    }
}

repositories {
    jcenter()
}

dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'
}
// コード生成
task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    // Bean のコードも生成する場合は以下を有効化
    //exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}

compileScala {
    dependsOn generate

    sourceSets.main.scala.srcDir qdslDestDir
}

clean {
    delete qdslDestDir
}

DB は MySQL を使用し、「JPA における一対多のリレーションシップ - EclipseLink」 で使ったものと同じテーブルを使用します。

使用する DB のテーブル定義 (DDL
CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `price` decimal(10,0) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `product_variation` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(20) NOT NULL DEFAULT 0,
  `color` varchar(10) NOT NULL,
  `size` varchar(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

サンプルアプリケーション1

それでは簡単なサンプルアプリケーションを作成し実行してみます。

ビルド定義

先程の build.gradle へ少しだけ手を加え、サンプルアプリケーション sample.SampleApp を実行するようにしました。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

ext.modelPackage = 'sample.model'
ext.qdslDestDir = 'src/main/qdsl-generated'
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

mainClassName = 'sample.SampleApp'

buildscript {
    ・・・
}
・・・
dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'

    runtime 'mysql:mysql-connector-java:5.1.36'
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}

task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    //exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}
・・・

generate タスクを実行すると以下のファイルが生成されます。

  • src/main/qdsl-generated/sample/model/QProduct.scala
  • src/main/qdsl-generated/sample/model/QProductVariation.scala

実行クラス

単純な insert・select 処理を実装しました。

src/main/scala/sample/SampleApp.scala
package sample

import com.querydsl.sql.dml.SQLInsertClause
import com.querydsl.sql.{SQLQuery, MySQLTemplates}

import java.util.Properties
import java.sql.DriverManager

import scala.collection.JavaConversions._

import sample.model.{QProduct, QProductVariation}

object SampleApp extends App {
    val conf = new Properties()
    conf.load(getClass.getClassLoader.getResourceAsStream("db.properties"))

    val con = DriverManager.getConnection(conf.getProperty("url"), conf)
    con.setAutoCommit(false)

    val templates = new MySQLTemplates()

    val p = QProduct as "p"
    val v = QProductVariation as "v"

    // product へ insert
    val pid: Long = new SQLInsertClause(con, templates, p)
        .set(p.name, s"sample${System.currentTimeMillis()}")
        .set(p.price, 1500L)
        .executeWithKey(p.id)

    // product_variation へ insert
    new SQLInsertClause(con, templates, v)
        .set(v.productId, pid).set(v.color, "Green").set(v.size, "L").addBatch()
        .set(v.productId, pid).set(v.color, "Blue").set(v.size, "S").addBatch()
        .execute()

    con.commit()

    val query = new SQLQuery(con, templates)

    // product と product_variation を join して select
    val res = query.from(p)
        .join(v).on(v.productId.eq(p.id))
        .where(p.price.between(1300, 2500))
        .select(p.id, p.name, p.price, v.color, v.size)
        .fetch()

    // id, name, price でグルーピング
    val groupedRes = res.groupBy(x => (x.get(p.id), x.get(p.name), x.get(p.price)))

    println(groupedRes)

    con.close()
}

DB 接続設定ファイル

DB の接続設定に以下のプロパティファイルを使用します。

src/main/resources/db.properties
url=jdbc:mysql://localhost:3306/jpa_sample?characterEncoding=utf8
user=root
password=

実行

実行結果は以下の通りです。

実行結果
> gradle run

:compileJava UP-TO-DATE
:generate
:compileScala
:processResources
:classes
:run

Map((3,sample1439089472290,1500) -> ArrayBuffer([3, sample1439089472290, 1500, Green, L], [3, sample1439089472290, 1500, Blue, S]))

サンプルアプリケーション2

次は Bean を使ったサンプルです。

ビルド定義

exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer を有効化し、Bean のコード生成を行うようにしました。

build.gradle
apply plugin: 'scala'
apply plugin: 'application'

ext.modelPackage = 'sample.model'
ext.qdslDestDir = 'src/main/qdsl-generated'
ext.dbUrl = 'jdbc:mysql://localhost:3306/jpa_sample?user=root'

mainClassName = 'sample.SampleApp2'

buildscript {
    ・・・
}
・・・
dependencies {
    compile 'com.querydsl:querydsl-scala:4.0.3'
    compile 'com.querydsl:querydsl-sql:4.0.3'
    compile 'org.scala-lang:scala-library:2.11.7'
    compile 'org.apache.commons:commons-dbcp2:2.1.1'

    runtime 'mysql:mysql-connector-java:5.1.36'
    runtime 'org.slf4j:slf4j-nop:1.7.12'
}

task generate << {
    def con = new com.mysql.jdbc.Driver().connect(dbUrl, null)

    def exporter = new com.querydsl.sql.codegen.MetaDataExporter()

    exporter.packageName = modelPackage
    exporter.targetFolder = new File(qdslDestDir)
    exporter.serializerClass = com.querydsl.scala.sql.ScalaMetaDataSerializer
    exporter.typeMappings = com.querydsl.scala.ScalaTypeMappings.create()
    // Bean のコード生成を有効化
    exporter.beanSerializerClass = com.querydsl.scala.ScalaBeanSerializer
    exporter.createScalaSources = true

    exporter.export(con.metaData)

    con.close()
}
・・・

generate タスクを実行すると以下のファイルが生成されます。

  • src/main/qdsl-generated/sample/model/Product.scala
  • src/main/qdsl-generated/sample/model/ProductVariation.scala
  • src/main/qdsl-generated/sample/model/QProduct.scala
  • src/main/qdsl-generated/sample/model/QProductVariation.scala

実行クラス

処理内容は、サンプルアプリケーション1 と同じですが、com.querydsl.scala.sql.SQL トレイトを使って Connection を直接扱わなくても済むようにしています。

SQL トレイトの tx メソッドへ DB 処理 (insert や select 等) を渡します。 tx では大まかに以下のような処理を実行するようです。

  • (1) DataSource から Connection 取得 (setAutoCommit を false へ設定)
  • (2) 引数で渡した処理の実行
  • (3) コミット or ロールバック
  • (4) Connectionclose

なお、Bean を使って insert する場合は populate メソッドを使います。

src/main/scala/sample/SampleApp2.scala
package sample

import com.querydsl.scala.sql.SQL
import com.querydsl.sql.{SQLTemplates, MySQLTemplates}

import org.apache.commons.dbcp2.BasicDataSourceFactory

import java.util.Properties
import javax.sql.DataSource

import scala.collection.JavaConversions._

import sample.model.{Product, ProductVariation, QProduct, QProductVariation}

// com.querydsl.scala.sql.SQL トレイトの実装
case class QueryDSLHelper(dataSource: DataSource, templates: SQLTemplates) extends SQL

object SampleApp2 extends App {
    val product = (name: String, price: Long) => {
        val res = new Product()
        res.name = name
        res.price = price
        res
    }

    val variation = (productId: Long, color: String, size: String) => {
        val res = new ProductVariation()
        res.productId = productId
        res.color = color
        res.size = size
        res
    }

    val conf = new Properties()
    conf.load(getClass.getClassLoader.getResourceAsStream("db.properties"))

    val dataSource = BasicDataSourceFactory.createDataSource(conf)
    val qdsl = QueryDSLHelper(dataSource, new MySQLTemplates())

    val p = QProduct as "p"
    val v = QProductVariation as "v"

    qdsl.tx {
        // product へ insert
        val pid = qdsl.insert(p)
            .populate(product(s"test${System.currentTimeMillis()}", 2000L))
            .executeWithKey(p.id)

        // product_variation へ insert
        qdsl.insert(v)
            .populate(variation(pid, "Red", "M")).addBatch()
            .populate(variation(pid, "Yellow", "F")).addBatch()
            .execute()
    }

    qdsl.tx {
        // product と product_variation を join して select
        val res = qdsl.from(p)
            .join(v).on(v.productId.eq(p.id))
            .where(p.price.between(1300, 2500))
            .select(p.id, p.name, p.price, v.color, v.size)
            .fetch()

        // id, name, price でグルーピング
        val groupedRes = res.groupBy(x => (x.get(p.id), x.get(p.name), x.get(p.price)))

        println(groupedRes)
    }
}

DB 接続設定ファイル

commons-dbcp2 用のプロパティファイルを使いました。

src/main/resources/db.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jpa_sample?characterEncoding=utf8
username=root
password=

実行

実行結果は以下の通りです。

実行結果
> gradle run

:compileJava UP-TO-DATE
:generate
:compileScala
:processResources
:classes
:run

Map((3,sample1439089472290,1500) -> ArrayBuffer([3, sample1439089472290, 1500, Green, L], [3, sample1439089472290, 1500, Blue, S]), (4,test1439089637936,2000) -> ArrayBuffer([4, test1439089637936, 2000, Red, M], [4, test1439089637936, 2000, Yellow, F]))