WildFly Swarm 2016.12.0で、互換性のない変更のひとつとして、JDBC Driverまわりの機能が入りました。
Announcing WildFly Swarm 2016.12.0 | Thorntail
「Bring-your-own JDBC driver」ってやつですね。
これまでWildFly Swarm側で各Driver向けのモジュールを用意していたのを、利用側が自分でDriverを指定すると自動検出するようになった模様。
また、DataSourceの設定もYAMLで書けそうなので、これはいいなぁと思い試してみることに。
準備
WildFly Swarmのバージョンは「2016.12.0」として、データベースはMySQLを使用します。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <scala.major.version>2.12</scala.major.version> <scala.version>${scala.major.version}.1</scala.version> <scala.maven.plugin.version>3.2.2</scala.maven.plugin.version> <failOnMissingWebXml>false</failOnMissingWebXml> <wildfly.swarm.version>2016.12.0</wildfly.swarm.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>bom</artifactId> <version>${wildfly.swarm.version}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jaxrs</artifactId> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>cdi</artifactId> </dependency> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> <scope>runtime</scope> </dependency> </dependencies>
パッケージングは、WARです。
<packaging>war</packaging>
サンプルコード
JPAのEntity/EntityManagerを使用しますが、エントリポイントはJAX-RSで実装することにします。
JPAのEntity。
src/main/scala/org/littlewings/wildflyswarm/datasource/Book.scala
package org.littlewings.wildflyswarm.datasource import javax.persistence.{Column, Entity, Id, Table} import scala.beans.BeanProperty @Entity @Table(name = "book") @SerialVersionUID(1L) class Book extends Serializable { @Id @BeanProperty var isbn: String = _ @Column @BeanProperty var title: String = _ @Column @BeanProperty var price: Int = _ }
JAX-RSリソースクラス。EntityManagerは、このリソースクラスに直接@PersistenceContextで注入します。
src/main/scala/org/littlewings/wildflyswarm/datasource/BookResource.scala
package org.littlewings.wildflyswarm.datasource import javax.enterprise.context.ApplicationScoped import javax.persistence.{EntityManager, PersistenceContext} import javax.transaction.Transactional import javax.ws.rs._ import javax.ws.rs.core.{Context, MediaType, Response, UriInfo} @Path("book") @ApplicationScoped @Transactional class BookResource { @PersistenceContext private[datasource] var entityManager: EntityManager = _ @GET @Path("{isbn}") @Produces(Array(MediaType.APPLICATION_JSON)) def find(@PathParam("isbn") isbn: String): Book = entityManager.find(classOf[Book], isbn) @PUT @Consumes(Array(MediaType.APPLICATION_JSON)) @Produces(Array(MediaType.APPLICATION_JSON)) def register(book: Book, @Context uriInfo: UriInfo): Response = { entityManager.persist(book) Response.created(uriInfo.getRequestUriBuilder.path(book.isbn).build()).build } }
ところで、CDI用のモジュールを入れないとEntityManagerがインジェクションできなかったっぽいのですが、そういうもの…?
DataSourceの設定
DataSourceの設定は、「project-stages.yml」で設定。
src/main/resources/project-stages.yml
swarm: datasources: data-sources: MySqlDs: driver-name: mysql connection-url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf-8&characterSetResults=utf-8&useServerPrepStmts=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&useSSL=false user-name: kazuhira password: password
設定の書き方は、このあたりを参考に。
Configuration overlays using stage properties
JDBC Driverの自動登録
ここで、driver-nameに「mysql」と書きましたが、この名前はどこから来ているかというと、こちらあたりかなと思います。
https://github.com/wildfly-swarm/wildfly-swarm/blob/2016.12.0/fractions/javaee/datasources/src/main/java/org/wildfly/swarm/datasources/runtime/drivers/MySQLDriverInfo.java#L37
こちらのパッケージに各種RDBMS向けの実装があるので、お使いのデータベースに合わせて確認しておくとよいでしょう。
https://github.com/wildfly-swarm/wildfly-swarm/tree/2016.12.0/fractions/javaee/datasources/src/main/java/org/wildfly/swarm/datasources/runtime/drivers
persistence.xml
最後に、JPAのpersistence.xmlを用意します。
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1"> <persistence-unit name="wildfly.swarm.ds.pu" transaction-type="JTA"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <jta-data-source>java:jboss/datasources/MySqlDs</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL57InnoDBDialect"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <!-- <property name="hibernate.hbm2ddl.auto" value="validate"/> --> </properties> </persistence-unit> </persistence>
DataSourceのJNDI名は、起動時に確認しました。
動作確認
それでは、確認してみましょう。
$ mvn package && java -jar target/jpa-datasource-0.0.1-SNAPSHOT-swarm.jar
起動時に、JDBC Driverを見つけたっぽいログが出力されます。
2016-12-11 15:42:22,373 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 18) WFLYJCA0005: Deploying non-JDBC-compliant driver class com.mysql.jdbc.Driver (version 5.1) 2016-12-11 15:42:22,388 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-4) WFLYJCA0018: Started Driver service with driver-name = mysql
DataSourceも登録された模様。
2016-12-11 15:42:22,931 INFO [org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-8) WFLYJCA0001: Bound data source [java:jboss/datasources/MySqlDs]
起動後に、データの登録。
$ curl -XPUT -i -H 'Content-Type: application/json' http://localhost:8080/book -d '{ "isbn": "978-4774183169", "title": "パーフェクト Java EE", "price": 3456 }' HTTP/1.1 201 Created Connection: keep-alive Location: http://localhost:8080/book/978-4774183169 Content-Length: 0 Date: Sun, 11 Dec 2016 06:46:26 GMT
取得。
$ curl -i http://localhost:8080/book/978-4774183169 HTTP/1.1 200 OK Connection: keep-alive Content-Type: application/json Content-Length: 75 Date: Sun, 11 Dec 2016 06:46:49 GMT {"isbn":"978-4774183169","title":"パーフェクト Java EE","price":3456}
OKそうです。
まとめ
WildFly Swarmで、JDBC Driverの自動登録とDataSourceのYAMLによる設定を試してみました。
JDBC DriverやDataSourceの設定をコードで表現しているのはちょっと微妙(できれば設定で書きたい)と思っていたので、
このあたりができるようになったのは個人的にはけっこう嬉しかったり。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/wildfly-swarm-scala-examples/tree/master/jpa-datasource
ハマったこと
いやー、かなりハマりました。
だいたいハマったのは、次のことです。
- DataSourceの作成が、「java -jar」でしか反映されない(デフォルトのH2しか作られない)
- EntityManagerをインジェクションできずにハマる
最後のはもういいんですけど(CDI入れたらなんとかなったので)、前者はかなりハマりました。
例えば、次のコマンドで実行すると
$ mvn wildfly-swarm:run
JDBC Driverは登録してくれるのですが
2016-12-11 15:53:26,654 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 16) WFLYJCA0005: Deploying non-JDBC-compliant driver class com.mysql.jdbc.Driver (version 5.1) 2016-12-11 15:53:26,672 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-4) WFLYJCA0018: Started Driver service with driver-name = mysql
DataSourceは、デフォルトのものを登録しようとします。
2016-12-11 15:53:27,063 INFO [org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-8) WFLYJCA0001: Bound data source [java:jboss/datasources/ExampleDS]
先ほどのproject-stages.ymlの内容が反映された場合(java -jarで実行した場合)は、ExampleDSは作成されません。
で、最終的にPersistenceUnitが作成できずにエラーになる、と…。
2016-12-11 15:53:34,397 ERROR [org.jboss.as.controller.management-operation] (main) WFLYCTL0013: Operation ("add") failed - address: (("deployment" => "jpa-datasource-0.0.1-SNAPSHOT.war")) - failure description: { "WFLYCTL0412: Required services that are not installed:" => ["jboss.naming.context.java.jboss.datasources.MySqlDs"], "WFLYCTL0180: Services with missing/unavailable dependencies" => [ "jboss.persistenceunit.\"jpa-datasource-0.0.1-SNAPSHOT.war#wildfly.swarm.ds.pu\" is missing [jboss.naming.context.java.jboss.datasources.MySqlDs]", "jboss.persistenceunit.\"jpa-datasource-0.0.1-SNAPSHOT.war#wildfly.swarm.ds.pu\".__FIRST_PHASE__ is missing [jboss.naming.context.java.jboss.datasources.MySqlDs]" ] }
これ、mainから実行しても同じでかなり困りました。
パッケージング後に、java -jarでUber JARから起動するとこの挙動は変わるのですが…なんか自分がやると、どんどん実行手段が
減っていってる気がするのはどうしてでしょう?(笑)