Gradle と Querydsl Scala を使った Querydsl SQL のコード生成
前回 の JPA に続き、今回は Gradle と Querydsl Scala を使って Querydsl SQL のコード生成を試します。
ソースは http://github.com/fits/try_samples/tree/master/blog/20150810/
はじめに
Querydsl SQL の場合は Scala も Java と同じ要領でコードを生成します。 (Java の場合は 「Gradle を使った Querydsl SQL のコード生成」 参照)
ただし、MetaDataExporter
へ Scala 用の 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)
Connection
のclose
なお、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]))