Gradle と Querydsl Scala を使った Querydsl JPA のコード生成
Gradle と Querydsl Scala を使って Querydsl JPA の Scala 用コード生成を試してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20150727/
はじめに
「Gradle を使った Querydsl JPA のコード生成」 ではアノテーションプロセッサを使って Querydsl JPA のコードを生成しましたが、Scala の場合は com.querydsl.codegen.GenericExporter
クラスを使うようです。
GenericExporter
でコード生成するには JPA のエンティティクラスをロードできなければなりません。 (つまり、エンティティクラスを事前にコンパイルしておく必要あり)
Gradle ビルド定義
エンティティクラスを Querydsl のコード生成前にコンパイルするため、今回は以下のようにエンティティクラスだけをコンパイルするタスク modelCompile
と Querydsl 用のコードを生成するタスク generate
を追加しました。
番号 | 概要 | タスク名 |
---|---|---|
(1) | エンティティクラスをコンパイル | modelCompile |
(2) | (1) のエンティティクラスを使って Querydsl JPA の Scala 用コードを生成 | generate |
(3) | (2) で生成したソースをビルド | compileScala |
(1) では src/main/scala-model
へ配置したエンティティクラスのソース (Scala) をビルドして build/classes/main
へ出力します。
(2) では com.querydsl.codegen.GenericExporter
を使って Scala 用の Querydsl JPA コードを src/main/qdsl-generated
へ生成します。
(3) で (2) の生成したソースをビルドできるように sourceSets.main.scala.srcDir
へ src/main/qdsl-generated
を追加しています。
なお、(2) で (1) のクラスをロードできるように buildscript
の classpath
へ build/classes/main
を追加しているのですが、これが原因で初回実行時や clean 直後は (1) と (2) を別々に実行する必要があります。
これは、build/classes/main
へクラスファイルが配置されていない状態 ((1) の実施前) で Gradle を実行すると given scan urls are empty. set urls in the configuration
とメッセージが出力され、以降のタスクで build/classes/main
をクラスパスとして認識しない事が原因です。
build.gradle
apply plugin: 'scala' // スキャン対象の JPA エンティティクラスのパッケージ名 ext.modelPackage = 'sample.model' // JPA エンティティクラスのソースディレクトリ ext.modelSourceDir = 'src/main/scala-model' // Querydsl のソース生成先ディレクトリ ext.qdslDestDir = 'src/main/qdsl-generated' buildscript { // JPA エンティティクラスのビルド結果の出力先ディレクトリ // buildscript の classpath へ設定する必要があるため、ここで定義している ext.destDir = "$buildDir/classes/main" repositories { jcenter() } dependencies { classpath 'com.querydsl:querydsl-codegen:4.0.2' classpath 'com.querydsl:querydsl-scala:4.0.2' classpath 'org.scala-lang:scala-library:2.11.7' classpath 'javax:javaee-api:7.0' // コード生成時に JPA エンティティクラスをロードさせるための設定 classpath files(destDir) } } repositories { jcenter() } dependencies { compile 'com.querydsl:querydsl-jpa:4.0.2' compile 'com.querydsl:querydsl-scala:4.0.2' compile 'org.scala-lang:scala-library:2.11.7' compile 'org.apache.commons:commons-dbcp2:2.1' compile 'javax:javaee-api:7.0' } // (1) JPA エンティティクラスをコンパイル task modelCompile(type: ScalaCompile) { // ソースディレクトリ source = modelSourceDir // クラスパスの設定 (buildscript のクラスパスを設定) classpath = buildscript.configurations.classpath // クラスファイルの出力先 destinationDir = file(destDir) // 以下が必須 (ファイル名やパスは何でも良さそう) scalaCompileOptions.incrementalOptions.analysisFile = file("${buildDir}/tmp/scala/compilerAnalysis/compileCustomScala.analysis") } // (2) Querydsl JPA の Scala 用コードを生成 task generate(dependsOn: 'modelCompile') { def exporter = new com.querydsl.codegen.GenericExporter() // コード生成先ディレクトリの設定 exporter.targetFolder = file(qdslDestDir) exporter.serializerClass = com.querydsl.scala.ScalaEntitySerializer exporter.typeMappingsClass = com.querydsl.scala.ScalaTypeMappings exporter.entityAnnotation = javax.persistence.Entity exporter.embeddableAnnotation = javax.persistence.Embeddable exporter.embeddedAnnotation = javax.persistence.Embedded exporter.skipAnnotation = javax.persistence.Transient exporter.supertypeAnnotation = javax.persistence.MappedSuperclass // Scala ソースの出力 exporter.createScalaSources = true // コード生成の実施 exporter.export(modelPackage) } // (3) ソースをビルド compileScala { // generate タスクとの依存設定 dependsOn generate // Querydsl のコード生成先ディレクトリを追加 sourceSets.main.scala.srcDir qdslDestDir } clean { delete qdslDestDir }
サンプルアプリケーション
それでは簡単なサンプルアプリケーションを作成し実行してみます。
ビルド定義
先程の build.gradle へ少しだけ手を加え、EclipseLink と MySQL を使ったサンプルアプリケーション sample.SampleApp
を実行するようにしました。
build.gradle
apply plugin: 'scala' apply plugin: 'application' ext.modelPackage = 'sample.model' ext.modelSourceDir = 'src/main/scala-model' ext.qdslDestDir = 'src/main/qdsl-generated' // 実行クラス mainClassName = 'sample.SampleApp' buildscript { ・・・ } ・・・ dependencies { compile 'com.querydsl:querydsl-scala:4.0.2' compile 'com.querydsl:querydsl-jpa:4.0.2' compile 'org.scala-lang:scala-library:2.11.7' compile 'org.apache.commons:commons-dbcp2:2.1' compile 'javax:javaee-api:7.0' // 実行用の依存ライブラリ runtime 'org.eclipse.persistence:eclipselink:2.6.1-RC1' runtime 'mysql:mysql-connector-java:5.1.36' runtime 'org.slf4j:slf4j-nop:1.7.12' } ・・・
JPA エンティティクラス
「JPA における一対多のリレーションシップ - EclipseLink」 で使った JPA エンティティクラスを Scala で実装し直しました。
src/main/scala-model/sample/model/Product.scala
package sample.model import javax.persistence._ import java.util.ArrayList import java.util.List import java.math.BigDecimal @Entity class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = _ var name: String = _ var price: BigDecimal = _ @OneToMany(fetch = FetchType.EAGER, cascade= Array(CascadeType.ALL)) @JoinColumn(name = "product_id") val variationList: List[ProductVariation] = new ArrayList() override def toString = s"Product(id: ${id}, name: ${name}, price: ${price}, variationList: ${variationList})" }
src/main/scala-model/sample/model/ProductVariation.scala
package sample.model import javax.persistence._ @Entity @Table(name = "product_variation") class ProductVariation { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = _ var color: String = _ var size: String = _ override def toString = s"ProductVariation(id: ${id}, color: ${color}, size: ${size})" }
実行クラス
Querydsl JPA を使った単純な検索処理を行います。
src/main/scala/sample/SampleApp.scala
package sample import sample.model.Product import sample.model.ProductVariation import sample.model.QProduct import com.querydsl.jpa.impl.JPAQuery import javax.persistence.Persistence import java.math.BigDecimal import scala.collection.JavaConversions._ object SampleApp extends App{ def product(name: String, price: BigDecimal, variationList: ProductVariation*) = { val res = new Product() res.name = name res.price = price variationList.foreach(res.variationList.add) res } def variation(color: String, size: String) = { val res = new ProductVariation() res.color = color res.size = size res } val emf = Persistence.createEntityManagerFactory("jpa") val em = emf.createEntityManager() val tx = em.getTransaction() tx.begin() val p1 = product( "sample" + System.currentTimeMillis(), new BigDecimal(1250), variation("White", "L"), variation("Black", "M") ) em.persist(p1) tx.commit() val p = QProduct as "p" val query = new JPAQuery[Product](em) // Querydsl JPA による検索 val res = query.from(p).where(p.name.startsWith("sample")).fetch() // 結果の出力 res.foreach(println) em.close() }
実行
「JPA における一対多のリレーションシップ - EclipseLink」 で使った DB や JPA 設定ファイルを使って実行します。
src/main/resources/META-INF/persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="jpa"> <class>sample.model.Product</class> <class>sample.model.ProductVariation</class> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa_sample" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="eclipselink.logging.level" value="FINE" /> </properties> </persistence-unit> </persistence>
初回実行時や clean 直後は、modelCompile
と generate
以降のタスクを分けて実行する必要があります。 (上の方でも書きましたが buildscript
の classpath
へ build/classes/main
を設定している事が原因です)
エンティティクラスのコンパイル (modelCompile タスクの実行)
> gradle modelCompile given scan urls are empty. set urls in the configuration :modelCompile
以下のファイルが生成されます。
- src/main/qdsl-generated/sample/model/QProduct.scala
- src/main/qdsl-generated/sample/model/QProductVariation.scala
実行結果 (run タスクの実行)
> gradle run :compileJava UP-TO-DATE :modelCompile :generate :compileScala :processResources :classes :run ・・・ Product(id: 3, name: sample1, price: 100, variationList: [ProductVariation(id: 4, color: Black, size: M), ProductVariation(id: 5, color: White, size: L)]) Product(id: 4, name: sample1437821487341, price: 1250, variationList: [ProductVariation(id: 6, color: White, size: L), ProductVariation(id: 7, color: Black, size: M)])