システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

文字列をBase64に変換する

はじめに

ファイルパス(もしくはそれに準ずる文字列)をURLパラメータに使いたいと思いましたが、そのままではエスケープをあれこれしないといけないし、マルチバイトが入ってくると長くなります。そこで文字列をバイナリー圧縮してBase64変換したら良いかな?と思ってやってみました。

開発環境

Java 11

何が嬉しいの?

  • 文字列にURLに使用不可な文字があっても使用できる*1
  • 文字列の難読化(暗号化ではない)ができる

変換処理

処理の流れは以下の通り

  1. 文字列をバイナリー圧縮する
  2. バイナリーをBase64に変換する
  3. URLに使用してはいけない文字列を置換する

デコードは、この逆をします。

実装

import java.util.Arrays;
import java.util.Base64;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class StringCompressor {

    private static final Logger logger = Logger.getLogger(StringCompressorImpl.class.getName());

    public String deflate(String value) {
        byte[] input = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        byte[] output = new byte[256];
        Deflater compresser = new Deflater();
        compresser.setInput(input);
        compresser.finish();
        int compressedDataLength = compresser.deflate(output);
        var trimed = Arrays.copyOfRange(output, 0, compressedDataLength);
        var base64 = Base64.getEncoder().encode(trimed);
        var encoded = new String(base64, java.nio.charset.StandardCharsets.UTF_8);

        //base64で使用されていてURLで使用されることが推奨されていない文字を置換
        var replaced = encoded
                .replaceAll("\\+", "_")
                .replaceAll("/", "-")
                .replaceAll("=", "!");

        return replaced;
    }

    public String inflate(String value) {
        try {
            // deflateで置換した文字列をbase64用に置き換える
            var base64 = value
                    .replaceAll("_", "+")
                    .replaceAll("-", "/")
                    .replaceAll("!", "=");

            var decoded = Base64.getDecoder().decode(base64);

            Inflater decompresser = new Inflater();
            decompresser.setInput(decoded);
            byte[] output = new byte[256];
            int resultLength = decompresser.inflate(output);
            decompresser.end();

            var result = new String(output, 0, resultLength, java.nio.charset.StandardCharsets.UTF_8);
            return result;
        } catch (DataFormatException ex) {
            logger.warning(() -> ex.getMessage() + "::String could not decode " + value + "::");
            return value;
        }
    }

}

参考情報

Base64 - Wikipedia

URLに使用できる文字、できない文字 – ysklog

さいごに

短い文字列だと変換後の文字列の方が長くなってしまうことがあります*2
本来の目的が「ファイルパスのような文字列をURLのパラメータとして使いたいため」なので、それは仕方ないかなぁと。
難読化はしていますが暗号化はしていないので、ご利用時には注意ください。

*1:受信後デコードが必要ですが

*2:おそらくほぼ確実に

JSFの本番環境用設定

JSFの本番環境用設定に関するメモ

web.xmlの切り替え

本番環境向けの設定

javax.faces.PROJECT_STAGEを使う - Challenge Engineer Life !

やっておくと良さそうなもの

パーフェクト Java EEより

javax.faces.FACELETS_SKIP_COMMENTS
javax.faces.FACELETS_REFRESH_PERIOD

NIO2のメモ

今更ながら…

レッスン:基本的なI/O(Java?チュートリアル > 重要なクラス)

パス操作(Java?チュートリアル > 重要なクラス > 基本的なI/O)

ディレクトリの作成と読取り(Java?チュートリアル > 重要なクラス > 基本的なI/O)

Javaファイル関連メモ2(Hishidama's Java Files Memo)

java:walkFileTreeメソッドの使い方 – サイゼントの技術ブログ

備忘録的なblog: NIO2で再帰的にファイルを扱う方法

WatchService で再帰的にディレクトリ監視で難あり - A Memorandum

WatchService 使い方メモ - Qiita

java - JSF + WatchService + Thread - Stack Overflow

JakartaBatchのメモ

Jakarta Batch

Java EE 7 jBatchの使い方──『Java EE 7徹底入門』番外編 第3回 - builder by ZDNet Japan

Java EE 7 検証環境構築(12) jBatch 簡易サンプル作成と Arquillian でユニットテスト | Glob

Chunk方式のStepを使ってみる

javaee7-samples/batch at master · javaee-samples/javaee7-samples · GitHub

jBatch(Java EE 7)をNetBeans+GlassFish+Mavenではじめました - Instructor's memo

jBatchで、Chunk、Decision、Batchletをつなげて遊んでみる - CLOVER🍀

jBatch RI を Java SE でまともに(JPA,CDI,Transactionalつきで)動かす #javaee - kencharosの日記

Glassfish jBatch Checkpoint - @//メモ

Glassfish jBatch JSLで書けること整理 - @//メモ

JBatch (Payara 4.1.153) · khasunuma/Payara Wiki · GitHub

GitHub - jGauravGupta/jBatchSuite: jBatch Suite allows the developers to design java batch application model and automates Java code generation(Java Batch 1.0 JSR - 352)

https://dspace.cvut.cz/bitstream/handle/10467/61785/F3-DP-2015-Milata-Tomas-java-ee-batch-editor.pdf?sequence=13&isAllowed=y

JPAをJUnitでテスト

WebAPIであればE2Eテストで十分かもしれませんが、各レイヤーでテストが出来る仕組みがあると後々便利です。
ということで 直近のエントリーで作った JPA(with NativeQuery)を拡張してJUnitでテストをしたいと思います。

はじめに

JPAのテスト実装は 調べると 多少はありました。
ただ Springを使う例はあれど、Java EE/Jakarta EE の例は思ったよりも少なかったです。 普通(?)の使い方はあるのですが、orm/xmlが絡むと極端に少なかったので まとめてみました。 また、EE(JPA)がDBベンダーの差分を抽象化してくれるということで H2をインメモリで使うことで可搬性の高いものにしているつもりです。

なお、今回の実装ですが、Jakarta EE8を使っています*1

リポジトリ

jakarta-ee9-sample/jpa-part-6-junit5 at main · vermeer-1977-blog/jakarta-ee9-sample · GitHub

環境など

環境設定

まずは環境設定周りから。

pom.xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.7.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>8.0.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
        <!--            
        <scope>runtime</scope>
        <optional>true</optional>
        -->
    </dependency>

    <!-- test -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>2.7.7</version>
        <scope>test</scope>
    </dependency>
    <!-- test -->

</dependencies>


<build>
        
    <!-- for unitTest-->
    <testResources>
        <testResource>
            <directory>src/test/resources</directory>
            <filtering>true</filtering>
        </testResource>
    </testResources>
    
    <plugins>
    
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <encoding>${project.build.sourceEncoding}</encoding>
                <compilerArgument>-proc:none</compilerArgument>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M5</version>
        </plugin>

(... Cargo周りは省略)

</project>

JUnitに関連する依存を足すときには、maven-surefire-pluginも追加します。
H2については、今回 実行するDBとしても使っているのでruntimeを外しました*3

JPA関連としては、JUnit(つまりJava SE)でJPAを実行するための依存org.eclipse.persistenceと、testフォルダの関連資産の読み込みをするためのtestResourcesの追記です。
参考にしたブログでは testResourcesについての言及が無かったので、ひょっとしたら orm.xmlを使わなければ不要な設定なのかもしれません*4

persistence.xml

<persistence-unit name="TEST_PU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    
        <!-- read main class -->
        <jar-file>${project.basedir}/target/classes</jar-file>

        <!-- read orm.xml  -->
        <mapping-file>META-INF/orms/orm.xml</mapping-file>
    
    <properties>
        <property name="javax.persistence.schema-generation.database.action" value="create"/>
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1"/>
        <property name="javax.persistence.jdbc.user" value="sa"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="eclipselink.logging.level" value="WARNING"/>
        <property name="eclipselink.logging.level.sql" value="FINE"/>
        <property name="eclipselink.logging.level.connection" value="WARNING"/>
    </properties>
</persistence-unit>

transaction-type="RESOURCE_LOCAL"とするのは、JUnitが EEコンテナではなく Java SEで実行されるからです。

ポイントは <jar-file>${project.basedir}/target/classes</jar-file> で mainフォルダでビルドした資産を testフォルダで参照します。
orm.xml は testフォルダでも参照するので、mainと同じパス<mapping-file>META-INF/orms/orm.xml</mapping-file>を指定します。
<jar-file> でパスとしては同じ場所にあるので 何も書かなくても test側でも参照するかと思ったのですが、そういうものではありませんでした。

なお、orm.xmlをMETA-INFの直下に1つだけおいた構成の場合は、<mapping-file>の記述が無くても読み込まれました*5

JUnit

DB起動と接続

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class JpaTestExtension implements BeforeAllCallback, AfterAllCallback {

    public static final String PU_NAME = "TEST_PU";

    private static EntityManagerFactory emf;
    public static EntityManager em;

    public static org.h2.tools.Server server;

    @Override
    public void beforeAll(ExtensionContext ec) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shutdownServer();
        }));

        server = org.h2.tools.Server.createTcpServer().start();
        emf = Persistence.createEntityManagerFactory(PU_NAME);
        em = emf.createEntityManager();
    }

    @Override
    public void afterAll(ExtensionContext ec) throws Exception {
        shutdownServer();
    }

    private static void shutdownServer() {
        if (em != null && em.isOpen()) {
            em.close();
            em = null;
        }

        if (emf != null && emf.isOpen()) {
            emf.close();
            emf = null;
        }
        if (server != null) {
            server.stop();
            server = null;
        }
    }

}

Extensionにして、JPAのテストをしたいケースに付与できるようにしました。 複数のテスト用PUを作った場合は、同様の Extensionを作って対応できます。
PUが1つだけであれば クラスにして継承でも実現できる機能ですが 複数のPUを指定できる @ExtendWithを使うやり方の方が良いと思います。

ちなみにPersistence.createEntityManagerFactory(PU_NAME)のところが なかなか解決できずに苦労しました。

テストコード

@ExtendWith(JpaTestExtension.class)
public class UserDataQueryImplTest {

    @Test
    public void test01() {
        var userData = new UserData(1L);
        userData.setNickName("NickTest");
        userData.setUserName("UserName1 Test2");

        var tx = JpaTestExtension.em.getTransaction();
        tx.begin();
        JpaTestExtension.em.persist(userData);
        tx.commit();

        var query = new UserDataQueryImpl(JpaTestExtension.em);
        var results = query.getUserData();

        assertEquals(1, results.size());
        assertEquals("NickTest", results.get(0).getNickName());
        assertEquals("UserName1", results.get(0).getUserName().getFirstName());
        assertEquals("Test2", results.get(0).getUserName().getLastName());
    }
}

先に作成したExtensionを @@ExtendWithで適用します。
staticなので、利用するときは JpaTestExtension.em のようにして参照します。
なお、PUが1つだけの場合は static importを使うと emだけになるので記述がよりスッキリします。

余談

EntityManagerですが、コンストラクタインジェクションできるようにしています。
Payara ServerのEE9 RCでは出来なかったのですが、EE8を対象にすると出来たので見直しをしています*6。 なお フィールドインジェクションでも、アクセス修飾子をdefault(無印)にしておけば Mockなど使わなくてもインジェクションは出来ます。

該当箇所の抜粋は以下。

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Dependent
public class ResourceProvider {

    @PersistenceContext
    private EntityManager entityManager;

    @Produces
    @Dependent
    public EntityManager getEntityManager() {
        return entityManager;
    }
}
@RequestScoped
public class UserDataQueryImpl implements UserDataQuery {

    private final EntityManager em;

    @Inject
    public UserDataQueryImpl(EntityManager em) {
        this.em = em;
    }

(...)
}

差分はコミットログを

Constructor Injection · vermeer-1977-blog/jakarta-ee9-sample@d6089d5 · GitHub

参考

JUnit 5 ユーザーガイド

JUnitでJPAのテスト | KATSUMI KOKUZAWA'S BLOG

テストケースからのH2起動 - A Memorandum

H2をインメモリで動かすときの注意 - Yamkazu's Blog

DBUnitとH2 Databaseを使ってみた。 - /dev/null

java - How to configure JPA for testing in Maven - Stack Overflow

JUnit5 使い方メモ - Qiita

さいごに

やろうとしていることは そんなに難しいはずはないのに 毎回毎回「ズバリ」が無いのは何故なんだろうと思います。
今回はテストクラス側で Persinstenceを取得するところで思ったようにいかず あれこれ試しました。
とはいえ、ハマるときは かなり長い時間途方にくれることが多いのですが*7GitHubで他の人の実装を検索するというのをやるようになって視点や観点の切り替えのヒントを得るようにしたら、以前よりは ハマる時間が減ったような気がします。
ということで、最近はブログのコードはGitHubへ上げるようにしています。

*1:Jakarta EE9 RCではありません。試したみたのですが うまくいかなかったためです

*2:EE9-RCではありません

*3:text,runtimeという書き方もあるのですがWARNINGが出るので scope自体を外すことにしました

*4:少なくとも 私は testフォルダのpersistance.xmlが読まれなかったため PersistenceUnitが取得出来ませんでした

*5:この辺りの差は良く分かりません。

*6:Payara Serverの公式対応はEE8なので、まだできないこともあるのは仕方ないことです

*7:技術力無いので

CargoでPayara Serverを動かす

はじめに

こちらで使用したCargoというEEサーバーを動かすための薄いラッパーライブラリについて、自分が使ってみて良かったことなどをまとめたものです。 ブログなどでコードを公開する際、特にJakarta EEのようなEEサーバーを使う前提のものは、Spring Boot や QuarkusのようなUberJarを用いるものと比べると 公開されているコードを実際に動かすまでの手間がかかって避けがちになるように思います。
Cargoを使えば、対象となるEEサーバーのダウンロードも含めて実行してくれるので プロジェクトを git clone した後、ほとんど手間なくEEサーバーで動きを含めて確認ができます。
今回は それに加えて Mavenも同時にダウンロードする Maven Wrapper(mvnw) や、H2DBも 準備しています。

EEサーバーがあって、H2DBという標準SQLが書ける*1、基本環境が さっと手元で動かせる雛形プロジェクトの事例として、以下の記事で実際にやったことを踏まえつつ説明をしていきたいと思います。

vermeer.hatenablog.jp

  • 注意点

本プロジェクトでは Payara Serverでしか動作は確認できていません*2
その点は ご了承ください。
参考に挙げている、@backpaper0さんのリポジトリでは、コードを一切書き換えることなく(!) WildFlyやTomEEを Cargoによって起動することが出来ますので、是非 そちらも参考にされると良いと思います。

リポジトリ

https://github.com/vermeer-1977-blog/jakarta-ee9-sample

の中のサブプロジェクト
https://github.com/vermeer-1977-blog/jakarta-ee9-sample/tree/main/jpa-part-0

を使って説明をしたいと思います。

実行環境

Maven Wrapper(mvnw)

なにが嬉しいの?

Cargoの前に、mvnwについて。
mvnwを使うとローカルにMavenがインストールされていなくても、コマンドを実行するだけで 必要なMavenライブラリをダウンロードをしてくれてプロジェクト管理が可能な状態にしてくれます。

Mavenのバージョンが違うという事で弊害にであったことは、私自身はありませんが ビルド環境のベースとなるライブラリが異なるという理由で ビルドが失敗するという経験は出来れば避けたいものです。 mvnwはダウンロードする対象バージョンもプロジェクト毎に固定化されるので、そういった心配もありません。

実際に動かしてみる

jpa-part-0 フォルダへ移動して

./mvnw package

をすると、Mavenダウンロードと加えて、コードをビルドして warファイルの作成までしてくれます。 作成された warファイルは targetフォルダ配下に格納されます。

自分で作りたい

利用側はMavenのインストールが不要ですが、プロジェクトを作る側はMavenのインストールが必要です。 Mavenをインストールした後で

mvn -N io.takari:maven:0.7.7:wrapper

を実行すると

.mvnというフォルダが作成されます。これが mvnwの本体です。
また、カレントフォルダには mvnwmvnw.cmd というファイルが作られます。こちらは 実行するコマンドです。

これで mvnwを使えるようになりました。

詳細は 公式を確認ください。
https://github.com/takari/maven-wrapper

注意点

mvnwを使うだけであれば、上記の通りで良いのですが、このままだと Gitの管理対象外のままなので.gitignoreへ以下を追記します *3

#exclude
!maven-wrapper.jar

を追記して、mvnwの本体をリポジトリの管理対象に入れてください。

Cargo

ようやく本題のCargoです。
コンテナのデータ取得から設定まで pom.xmlで完結します。 pom.xmlの どこで 何をしているのか Cargo周りを中心に説明します。 DB周りや個々の設定意図については、後述します。

なにが嬉しいの?

DockerのようにOSイメージまでダウンロードをせず、「JVMが動くこと」を前提とした それ以降のレイヤーの資産をダウンロードおよび実行までしてくれるコンテナが準備可能という事です(つまり軽い)。

たとえば ちょっとしたデモ環境を作りたいときに 実行環境となるPCの環境設定を一切変えることなく EEアプリを動かせます*4

pom.xml(抜粋)

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.cargo</groupId>
            <artifactId>cargo-maven3-plugin</artifactId>
            <version>1.9.4</version>
            <configuration>
                <configuration>
                    <datasources>
                        <datasource>
                            <jndiName>jdbc/DemoDS</jndiName>
                            <driverClass>org.h2.Driver</driverClass>
                            <url>jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1</url>
                            <username>demouser</username>
                            <password>demosecret</password>
                        </datasource>
                    </datasources>
                </configuration>
                <container>
                    <dependencies>
                        <dependency>
                            <groupId>com.h2database</groupId>
                            <artifactId>h2</artifactId>
                        </dependency>
                    </dependencies>
                    <systemProperties>
                        <javax.persistence.schema-generation.database.action>create</javax.persistence.schema-generation.database.action>
                        <javax.persistence.schema-generation.scripts.action>create</javax.persistence.schema-generation.scripts.action>
                        <javax.persistence.schema-generation.scripts.create-target>${project.basedir}/target/create.sql</javax.persistence.schema-generation.scripts.create-target>
                        <javax.persistence.sql-load-script-source>META-INF/tmp/initializer.sql</javax.persistence.sql-load-script-source>
                        <eclipselink.logging.level>FINE</eclipselink.logging.level>
                    </systemProperties>
                </container>
            </configuration>
        </plugin>
    </plugins>
</build>

<profiles>
    <profile>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <id>payara</id>
        <properties>
            <cargo.maven.containerId>payara</cargo.maven.containerId>
            <cargo.maven.containerUrl>https://repo.maven.apache.org/maven2/fish/payara/distributions/payara/5.2021.4/payara-5.2021.4.zip</cargo.maven.containerUrl>
        </properties>
    </profile>
</profiles>

maven(build)

Cargoのプラグイン(cargo-maven3-plugin)、build定義に追記します。

datasource

利用するDBの設定をします。 この設定は「Cargoで起動したEEサーバーの設定」です。
Javaアプリ(persistence.xml)とEEサーバーとの関連付けは、 <jndiName>jdbc/DemoDS</jndiName> です。

なので、usernamepasswordを demouser や demosecret から変更しても、実際のところ 問題なくCargoで起動したWebアプリは動きます*5

じゃぁ、そのDB本体(ミドルウェア)は どこから入手しているの?というのは後述します(環境設定周り)。

systemProperties

システムプロパティも指定が可能です。
このシステムプロパティも先ほどと同様に「Cargoで起動したEEサーバーの設定」です。

javax.persistence.の記載の意図などについては後述します(環境設定周り)。

maven(profile)

利用する EEサーバーの本体となるファイルのダウンロードおよび、コマンド実行時の挙動を設定します。

id

コマンドでパラメータ指定する際に利用します。

複数のEEサーバーを定義した際には <activeByDefault>true</activeByDefault>を付与している要素がデフォルトのEEサーバーとして起動できます。

任意のサーバーをパラメータで指定する場合は

./mvnw cargo:run -Ppayara

のように idを指定すれば実行できます。

properties

Payara Serverのzipファイルの取得先を指定します。

cargo:runした時に、zipファイルがダウンロードされていなければジョブの開始時点でダウンロードをします。ダウンロードして展開したEEサーバーの実体は実行パスのカレントに作成される targetフォルダの中にあります。

ダウンロード先や設定の詳細は公式を確認してみてください。

https://codehaus-cargo.github.io/cargo/Home.html

ちなみに fileプロトコルを使えば ローカルに保存しているzipファイルを取得先として指定できます*6

<cargo.maven.containerUrl>file:/home/hoge/Downloads/payara-5.2021.4.zip</(...)>

Windowsであればfile:c:\(以下略)という感じで指定すれば出来ると思います(未検証)。

注意点

Cargoが利用するEEサーバーの実体は targetフォルダにあるため ./mvnw cleanなどでtargetフォルダを削除した場合、再ダウンロードをします。
検証中で なんども cleanを実行するけれど ダウンロードの頻度は避けたい時は、上記の fileプロトコルを使った取得に書き換えると作業が捗ります。

環境設定周り

コンテナに関する環境設定の詳細と意図について説明したいと思います。

なにが嬉しいの?

  • コンテナで利用するDB(H2)を立ち上げる(個別にインストールしなくて良い)
  • @Entityクラスと同期のとれたテーブルが生成される
  • データの初期登録もできる
  • 上記の設定に関する記述をコードに記述しなくて良い
  • warファイルに設定が含まれないので、独立して準備したEEサーバーや開発環境のために書き直しなどをしなくて良い

H2を利用する

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>     

実行時依存として追加しておくことで、コンテナ起動時にH2をDBとして使えるようになります。 接続に関する説明は既出のため割愛します。

@EntityクラスからDBを生成

<javax.persistence.schema-generation.database.action>create</(...)> で@Entityクラスの実装から、コンテナ起動時にH2へテーブルを生成します。

もしDDLがある場合は、そちらを使ってテーブル生成しても良いと思います(その際のプロパティについては割愛します)。

DBからDDLを生成

<javax.persistence.schema-generation.scripts.action>create</(...)> でDBからテーブル生成DDLを作成します。

<javax.persistence.schema-generation.scripts.create-target>${project.basedir}/target/create.sql</(...)> は生成したDDLの出力先です。

このDDLを使えば、コンテナ起動時に自動生成されるDBを手元のDB環境で作ることも出来ます。(この辺りの意図は後述(開発IDEとの連携)で)

DBへ初期データを登録

<javax.persistence.sql-load-script-source>META-INF/tmp/initializer.sql</(...)> でコンテナ起動時にデータを初期登録できます。
指定パスは クラスパス配下です*7

SQLの実行ログ

<eclipselink.logging.level>FINE</(...)>

この辺りは実装依存です。
Payara Serverは JPAの実装が EclipseLinkなので このように指定します。 Hibernate が実装となっているEEサーバーの場合は指定方法が違います。

開発IDEとの連携

Cargoとは直接関係ないのですが、これまでの設定は この「開発IDEとの連携」を見越して行ったものもあるので説明をしたいと思います。

EEサーバー

Cargoは実行環境ではありますが、開発時に接続するサーバーと比べて あれこれとしようと思うと少し手間がかかります。
たとえばIDEによるデバッグモード起動などはローカルにインストールしたEEサーバーを使用する方が、既知の開発方法を流用できるので便利です。

Cargoの設定は Cargoに閉じています*8
IDEにてローカルにインストールしたEEサーバーを選択して これまで通り開発ができます。

テーブル生成

CargoではH2を使いましたが、本番環境では異なるDBかもしれません。
またCargoでは テストのために作り込んだDBの状態が起動の都度クリアされてしまいます。

Jakarta Persistence(旧JPA)のJPQLや 標準SQLの範囲で、適切に抽象化されていれば DB製品が異なっても利用可能です*9
ということで、Cargo管理下とは別にDBをつくって開発を進めましょうということになるわけですが、空のDBは各DBの手順に従うとして、その先のテーブル生成はどうしましょう?

<javax.persistence.schema-generation.database.action>create</(...)>を使って、@Entityから生成をするという方法もあるでしょう。
では DDLを使ってテーブルを生成したい場合は、どうしましょうか?

ということで<javax.persistence.schema-generation.scripts.(...)で作成したDDLを使いましょう。

卵とヒヨコみたいな感じですが、とにかく入手したDDLを元に開発環境や本番環境のテーブル生成ができます。

なお、Cargoで設定した時には、その値に特に重きを置いていなかった、usernamepasswordですが、 EEサーバーで JDBCのコネクションプールの設定や、直接DBへの接続をするためには必要な情報となります。

DBの接続設定

DBの接続情報を、利用するEEサーバーにも設定をしましょう。
JNDIは Cargoと同じ jdbc/DemoDS としてください。こうすることでDBの実体は違ってもプロジェクトの定義を変えることなく起動できます。
IDとパスワードは前項の「テーブル生成」で設定した値を使用してください。

なお、IDおよびパスワードですが、Cargoで設定したものとは必ず別の値にしましょう。
リポジトリ管理される資産に有効なIDとパスワードは記録すべきではありません。

参考

github.com

GitHub - takari/maven-wrapper: The easiest way to integrate Maven into your project!

Codehaus Cargo - Home

事のキッカケの自tweet

さいごに

Jakarta PersistenceのNativeQueryを使う記事で、初めてCargoを使いました。 他にも色々と便利な機能などありそうな気がしますが、DBを使うアプリの実行および開発環境準備まで含めた やっておきたいことは試せたように思います。
可能な限り、今後の Jakarta EEに関するブログを書くときには、Cargoを使って動くサンプル実装が作られたらと思います。

*1:PosgreSQL互換モードもある

*2:というか TomEEなどでは動かないという確認もしています

*3:Java向けの設定では一般的に jarファイルを対象外としています

*4:JDKのインストールは必要だけど

*5:実際に変えてみて動くことまでは確認しました

*6:当然ですが、一度は自分でローカルにzipファイルを保存しておく必要はあります

*7:Javaコードから読み込める場所にないといけないですからね

*8:portの衝突があるので、コンテナは終了させておきましょう

*9:厳密に言えば「EEの仕様としては そのように推定される」という言い方になるかもしれませんが