CLOVER🍀

That was when it all began.

WildFly Bootable JARに、DataSourceを組み込む

これは、なにをしたくて書いたもの?

WildFly Bootable JAR Maven Pluginを使って、Bootable JARを作成してみました。

WildFly Bootable JARを試す - CLOVER🍀

今度は、Bootable JARにDataSourceを組み込んでみたいと思います。

WildFly DataSources Galleon Feature Pack

WildFly Bootable JARにDataSourceを組み込むためには、Feature Packを使用します。

GitHub - wildfly-extras/wildfly-datasources-galleon-pack: WildFly Feature Pack for DataSources

こちらをWildFly Bootable JAR Maven PluginにFeature Packおよびlayerに組み込むことで、JDBCドライバのデプロイと
DataSourceの作成を行うことができます。

対応しているデータベースは、

となります。

今回は、MySQLのJDBCドライバおよびDataSourceを使ってみましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-77-generic", arch: "amd64", family: "unix"

MySQLは8.0.25を使い、172.17.0.2で動作しているものとします。

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

まずは、WildFlyにデプロイするためのアプリケーションを作成します。

DataSourceを使いたいので、JPAを使うことにしましょう。あとは、JAX-RSとCDI、JTAも使う感じで。

まずはpom.xml。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>wildfly-bootable-jar-with-datasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>5.0.1.Final</version>
                <configuration>
                    <!-- 後で -->
                </configuration>
            </plugin>
        </plugins>
</project>

JAX-RSの有効化。

src/main/java/org/littlewings/jakartaee/wildfly/bootable/JaxrsActivator.java

package org.littlewings.jakartaee.wildfly.bootable;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("")
public class JaxrsActivator extends Application {
}

JPAのエンティティクラス。

src/main/java/org/littlewings/jakartaee/wildfly/bootable/Book.java

package org.littlewings.jakartaee.wildfly.bootable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "book")
public class Book {
    @Id
    private String isbn;

    private String title;

    private Integer price;

    // getter/setterは省略
}

EntityManagerを使う、JAX-RSリソースクラス。

src/main/java/org/littlewings/jakartaee/wildfly/bootable/BookResource.java

package org.littlewings.jakartaee.wildfly.bootable;

import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@ApplicationScoped
@Path("book")
public class BookResource {
    @PersistenceContext
    EntityManager entityManager;

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public Book find(@PathParam("isbn") String isbn) {
        return entityManager.find(Book.class, isbn);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public List<Book> findAll() {
        return entityManager
                .createQuery("select b from Book b order by b.price desc", Book.class)
                .getResultList();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public Book register(Book book) {
        if (entityManager.find(Book.class, book.getIsbn()) != null) {
            entityManager.merge(book);
        } else {
            entityManager.persist(book);
        }

        return book;
    }
}

Persistence Unitの設定は、こちら。DataSourceを使うようにしています。

src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<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_2.xsd"
             version="2.2">
    <persistence-unit name="wildfly.bootable.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.MySQL8Dialect"/>
            <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"/>
        </properties>
    </persistence-unit>
</persistence>

簡単に済ませたいので、Entityに対応するテーブルはHibernateで作成するようにしました。

WildFly DataSources Galleon Feature Packを設定する

では、WildFly DataSources Galleon Feature Packを使って、WildFly Bootable JAR Maven Pluginを設定していきましょう。

参考にするとよいサンプルは、こちらですね。

https://github.com/wildfly-extras/wildfly-jar-maven-plugin/tree/5.0.1.Final/examples/postgresql

WildFly DataSources Galleon Feature Packには、データベースの種類ごとにREADME.mdが用意されています。
MySQLの場合はこちら。

https://github.com/wildfly-extras/wildfly-datasources-galleon-pack/blob/2.0.2.Final/doc/mysql/README.md

まずは、WildFly DataSources Galleon Feature PackをWildFly Bootable JAR Maven Pluginの設定に追加します。

                    <feature-packs>
                        <feature-pack>
                            <location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</location>
                        </feature-pack>
                        <feature-pack>
                            <groupId>org.wildfly</groupId>
                            <artifactId>wildfly-datasources-galleon-pack</artifactId>
                            <version>2.0.2.Final</version>
                        </feature-pack>
                    </feature-packs>

feature-packsを使用すると、feature-pack-location要素は使えなくなります。WildFlyのどのバージョンを使うかは、
feature-pack、locationとして指定することになります。

Providing a list of Galleon feature-packs

wildfly-datasources-galleon-packを追加すると、Galleon layerが追加されます。

MySQLの場合はmysql-datasourceとmysql-driverが使えるようになります。

                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>mysql-datasource</layer>
                        <layer>mysql-driver</layer>
                        <layer>management</layer>
                    </layers>

mysql-datasourceは非XA DataSourceをプロビジョニングするlayerで、mysql-driverに依存しています。

なので、mysql-driver layerは、実は明示しなくてもかまいません。

                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>mysql-datasource</layer>
                        <!-- <layer>mysql-driver</layer>  -->
                        <layer>management</layer>
                    </layers>

mysql-driverは、MySQLのJDBCドライバをプロビジョニングするlayerです。

MySQLに限らず、JDBCドライバをプロビジョニングするlayerを使う場合、どのバージョンのJDBCドライバが使われるかは
wildfly-datasources-galleon-pack-parentのpom.xmlで確認できます。

wildfly-datasources-galleon-pack/pom.xml at 2.0.2.Final · wildfly-extras/wildfly-datasources-galleon-pack · GitHub

wildfly-datasources-galleon-packの2.0.2.Finalの場合、MySQLのJDBCドライバは8.0.25がプロビジョニングされます。

ここまでの、全体の設定はこんな感じです。

            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>5.0.1.Final</version>
                <configuration>
                    <feature-packs>
                        <feature-pack>
                            <location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</location>
                        </feature-pack>
                        <feature-pack>
                            <groupId>org.wildfly</groupId>
                            <artifactId>wildfly-datasources-galleon-pack</artifactId>
                            <version>2.0.2.Final</version>
                        </feature-pack>
                    </feature-packs>
                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>mysql-datasource</layer>
                        <!-- <layer>mysql-driver</layer>  -->
                        <layer>management</layer>
                    </layers>
                    <excluded-layers>
                        <layer>deployment-scanner</layer>
                    </excluded-layers>
                    <plugin-options>
                        <jboss-fork-embedded>true</jboss-fork-embedded>
                    </plugin-options>
                </configuration>
            </plugin>

deployment-scannerは不要なので外しています。

jboss-fork-embeddedというのは、Mavenセッションで多くのBootable JARを構築する際にtrueとすることが推奨されて
いるのですが、ほとんどのサンプルがtrueになっているので合わせることにしました。

pluginOptions

これで、パッケージングします。
※executionは設定していません

$ mvn package wildfly-jar:package

起動してみます。

$ java -jar target/wildfly-bootable-jar-with-datasource-0.0.1-SNAPSHOT-bootable.jar

すると、MySQLのJDBCドライバがデプロイされますが

18:59:34,714 INFO  [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 24) WFLYJCA0005: Deploying non-JDBC-compliant driver class com.mysql.cj.jdbc.Driver (version 8.0)
18:59:34,725 INFO  [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-7) WFLYJCA0018: Started Driver service with driver-name = mysql

起動時にいくつかERRORも表示されます。

18:59:34,807 ERROR [org.jboss.as.controller.management-operation] (ServerService Thread Pool -- 24) WFLYCTL0013: Operation ("add") failed - address: ([
    ("subsystem" => "datasources"),
    ("data-source" => "MySQLDS")
]) - failure description: "WFLYCTL0211: Cannot resolve expression '${org.wildfly.datasources.mysql.user-name,env.MYSQL_USER,env.OPENSHIFT_MYSQL_DB_USERNAME}'"


18:59:36,786 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("add") failed - address: ([
    ("subsystem" => "datasources"),
    ("data-source" => "MySQLDS")
]) - failure description: "WFLYCTL0211: Cannot resolve expression '${org.wildfly.datasources.mysql.user-name,env.MYSQL_USER,env.OPENSHIFT_MYSQL_DB_USERNAME}'"
18:59:36,788 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("add") failed - address: ([("deployment" => "wildfly-bootable-jar-with-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.\"ROOT.war#wildfly.bootable.pu\" is missing [jboss.naming.context.java.jboss.datasources.MySqlDs]",
        "jboss.persistenceunit.\"ROOT.war#wildfly.bootable.pu\".__FIRST_PHASE__ is missing [jboss.naming.context.java.jboss.datasources.MySqlDs]"
    ]
}

これは、DataSourceをプロビジョニングしたものの、設定をしていないからですね。

DataSourceの設定は、こちらを参照して行います。

Configuration

設定方法は環境変数、もしくはシステムプロパティで行います。必須の設定とオプションの設定があります。

今回は、システムプロパティで行うことにしましょう。

$ java \
  -Dorg.wildfly.datasources.mysql.connection-url='jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8' \
  -Dorg.wildfly.datasources.mysql.user-name=kazuhira \
  -Dorg.wildfly.datasources.mysql.password=password \
  -Dorg.wildfly.datasources.mysql.jndi-name=java:jboss/datasources/MySqlDs \
  -jar target/wildfly-bootable-jar-with-datasource-0.0.1-SNAPSHOT-bootable.jar

JNDI名は必須ではないのですが、今回は明示的に指定することにしました。

今度は、ERRORは表示されません。

動作確認してみます。

$ curl -XPOST -H 'Content-Type: application/json' http://localhost:8080/book -d '{"isbn": "978-4774183169", "title": "パーフェクト Java EE", "price": 3520}'
{"isbn":"978-4774183169","price":3520,"title":"パーフェクト Java EE"}


$ curl http://localhost:8080/book
[{"isbn":"978-4774183169","price":3520,"title":"パーフェクト Java EE"}]


$ curl http://localhost:8080/book/978-4774183169
{"isbn":"978-4774183169","price":3520,"title":"パーフェクト Java EE"}

OKです。

動作確認できました。

つまり、WildFly DataSources Galleon Feature Packを使ったDataSourceの設定は、アプリケーションの実行時に指定することが
わかりました。

開発モードで使う

ところで、個人的にはDataSourceも開発モードで使いたいものです。つまり、以下のようなコマンドですね。

$ mvn wildfly-jar:dev-watch


$ mvn wildfly-jar:dev

今回はwildfly-jar:dev-watchを使いたいと思います。

DataSourceの設定は実行時に行うことがわかりましたが、開発モードで使う時にはどうも相性が悪いようです(mvnコマンド
実行時に指定しても無視される)。

どうしようかなと思いましたが、WildFly CLIでDataSourceを設定することにしました。

Configuring the server during packaging

DataSourceを作成するスクリプトを作成して

jboss-cli-scripts/script1.cli

data-source add \
  --name=MySqlDs \
  --jndi-name=java:jboss/datasources/MySqlDs \
  --user-name=kazuhira \
  --password=password \
  --driver-name=mysql \
  --connection-url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8

WildFly Bootable JAR Maven Pluginは、こんな感じに設定。

            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-jar-maven-plugin</artifactId>
                <version>5.0.1.Final</version>
                <configuration>
                    <feature-packs>
                        <feature-pack>
                            <location>wildfly@maven(org.jboss.universe:community-universe)#24.0.0.Final</location>
                        </feature-pack>
                        <feature-pack>
                            <groupId>org.wildfly</groupId>
                            <artifactId>wildfly-datasources-galleon-pack</artifactId>
                            <version>2.0.2.Final</version>
                        </feature-pack>
                    </feature-packs>
                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>mysql-driver</layer>
                        <layer>management</layer>
                    </layers>
                    <excluded-layers>
                        <layer>deployment-scanner</layer>
                    </excluded-layers>
                    <cli-sessions>
                        <cli-session>
                            <script-files>
                                <script>jboss-cli-scripts/script1.cli</script>
                            </script-files>
                        </cli-session>
                    </cli-sessions>
                    <plugin-options>
                        <jboss-maven-dist/>
                        <jboss-fork-embedded>true</jboss-fork-embedded>
                    </plugin-options>
                </configuration>
            </plugin>

先ほどとの差は、layerからmysql-datasourceを外してmysql-driverのみとし

                    <layers>
                        <layer>jaxrs-server</layer>
                        <layer>mysql-driver</layer>
                        <layer>management</layer>
                    </layers>

WildFly CLIスクリプトを実行するため、cli-sessionsを追加。

                    <cli-sessions>
                        <cli-session>
                            <script-files>
                                <script>jboss-cli-scripts/script1.cli</script>
                            </script-files>
                        </cli-session>
                    </cli-sessions>

あとはJARファイルを小さくするためにjboss-maven-distも入れておきます。

                    <plugin-options>
                        <jboss-maven-dist/>
                        <jboss-fork-embedded>true</jboss-fork-embedded>
                    </plugin-options>

では、wildfly-jar:dev-watchで起動。

$ mvn wildfly-jar:dev-watch

実行中に、WildFly CLIが実行されている様子が確認できます。

[INFO] Executing CLI, CLI Session, scripts=[jboss-cli-scripts/script1.cli], resolve-expressions=true, properties-file=null
[INFO] CLI scripts execution done.

これでDataSourceが作成されるので、あとはふつうに使えます。

WildFly CLI向けのスクリプトですが、パッケージングだけではなく起動時にも適用できるようなので、これはこれで
覚えておいた方が良さそうですね(--cli-script=<path to CLI script file>)。

Bootable JAR arguments

オマケ

データソースを作成するために、デプロイされるJDBCドライバがどの名前で登録されるのかをログから確認して
いたのですが、よく見るとこちらで確認できますね。

https://github.com/wildfly-extras/wildfly-datasources-galleon-pack/blob/2.0.2.Final/galleon-feature-pack/src/main/resources/layers/standalone/mysql-driver/layer-spec.xml#L5

また、JBoss Moduleとしての定義もこちらにあります。

https://github.com/wildfly-extras/wildfly-datasources-galleon-pack/blob/2.0.2.Final/galleon-feature-pack/src/main/resources/modules/com/mysql/jdbc/main/module.xml

このあたりを見ていると、なんとなくFeature Packを作れるようになるんじゃないか、という気になってきますね。
そういう気になるだけですが。

まとめ

WildFly Bootable JARに、DataSourceを組み込んでみました。

開発モードでも使えた方が良いな、と思っていたので、そのあたりもクリアできる方法がわかってよかったかなと思います。