CLOVER🍀

That was when it all began.

WildFly Swarmで、JDBC Driverの自動登録機能とDataSourceの設定を使う

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

JDBC Driverの名前は、「mysql」です。

設定の書き方は、このあたりを参考に。
Configuration overlays using stage properties

PostgreSQL の利用

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

Connector/J 6.0

MySQLJDBC Driver(Connector/J)ですが、最新版は6.0系となっていますが、今回は5.1系を採用しています。

自動登録のログを見ていて、ちょっと「おや…?」という感じになったのと、もともとのWildFly SwarmのMySQL JDBC Driver向けのモジュールが5.1系を使用していたようなので、ここは合わせておきました。

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から起動するとこの挙動は変わるのですが…なんか自分がやると、どんどん実行手段が
減っていってる気がするのはどうしてでしょう?(笑)