Helidon and JPA

原文はこちら。
The original entry was written by Laird Nelson.
https://medium.com/helidon/helidon-and-jpa-da20492f5395

HelidonがマネージドJPA 2.2を完全にサポートしていることをご存知でしょうか。

Jakarta Persistence 2.2
https://jakarta.ee/specifications/persistence/2.2/

おそらく使い慣れた単純なJava SEモードのJava Persistence APIよりもはるかに優れています。(開発者の中には)Persistence.createEntityManagerFactory()を呼び出してからEntityManagerを自分で管理し、例外やスレッドの安全性、トランザクションやロールバック、その他の多くの悩みの種を自身で処理しつつ頭痛を自分で処理するのに慣れてらっしゃる方もいらっしゃるかもしれませんが、(この作業自体は)面白くないですね。

それとは異なり、Helidon MPではこうした問題をすべて管理してくれます。Helidon MPのマネージドJPAサポートは、Java EEアプリケーションサーバー用のコードを作成する場合と同様の、便利で宣言的な方法で扱えますが、Helidon MPは、アプリケーションサーバーではないことに加えて、従来のアプリケーションサーバーよりも小さく、軽く、高速です。これは両方のいいとこ取りです。

Helidon MP
https://helidon.io/docs/latest/#/about/02_mp-about

このエントリでは、JPA機能を標準のHelidon MPのサンプルプロジェクトに追加し、慣れたJava EEライクな方法でJPA機能の操作する方法をご紹介します。主要なソースを確認したい方のために、数多くのバックグランドリンクを追加しました。そしてすべてのピースがどのように結び付いているかを理解できるように、詳細を詳しく説明していきます。

Creating the Project

まず、Maven archetypeを使ってMavenプロジェクトを新規作成します。

Introduction to Archetypes
https://maven.apache.org/guides/introduction/introduction-to-archetypes.html

この例では、JDK 11とMaven 3.6.3がインストールされているものとします。cdでプロジェクトを作成したいディレクトリに入り、以下のコマンドを実行します。

このコマンドは、Helidonのarchetypeの1つを使用して、helidon-jpaという名前の単純なHelidon MPベースのプロジェクトを作成します。このエントリでは、このプロジェクトを拡張して、Helidon MPのマネージドJPAサポートを使用します。

Helidon archetypes
https://github.com/oracle/helidon/tree/master/archetypes

JPA Components

Helidon MPのマネージドJPAサポートは、モノリシックではなく、疎結合コンポーネントのセットとして実装されています。必要なものだけを取り、Mavenプロジェクトに入れれば、最終的にJPAアプリケーションを実行するための調整された無駄のない環境を得られます。そのため、まず最初に新規プロジェクトがJPAの機能を使うために連携するコンポーネントについて、新しいプロジェクトが確実に認識できるようにすることです。

JPA自体は基本的にコンポーネント指向です。データベースドライバは接続プールが管理しています。この接続プールは、データベースの更新を管理するためにEntityManagerが協調するトランザクションエンジンによって管理されています。Helidon MPを使ってこうしたコンセプトを明示するために必要なコンポーネントを差し込むことができます。順にそれぞれ見ていきましょう。

Database Driver

最初の必要なコンポーネントはJDC準拠のデータベースドライバです。この例では、H2 databaseのドライバを使います。

H2 Database Engine
https://www.h2database.com/html/main.html

以下の依存関係をpom.xmlの<dependencies>セクションに追加します。

これでプロジェクトはH2 databaseを認識します。

別のデータベースを使っている場合、当該データベースのドライバのJARファイルを使ってください。

Connection Pool

次に必要なコンポーネントはデータベース接続プールです。Helidon MPは以下の接続プールをサポートしています。

この例では、HikariCP接続プールを選択します。以下の依存関係をpom.xmlの<dependencies>セクションに追加します。

これでプロジェクトはデータベースへの接続をプールする方法を認識します。

この接続プールを任意のJDBC準拠のデータベースドライバとともに利用できます(このコンポーネントを追加することで、必要に応じてコードにデータソースを直接入れることもできます)。

Transaction Manager

次に追加するコンポーネントは、Java Transaction API(JTA)準拠のトランザクションマネージャです。HelidonはNarayanaトランザクションエンジンをサポートしています。

Narayana
https://narayana.io/

このコンポーネントを使うと、JPAアプリケーションはトランザクションの手動での設定・破棄や、ロールバック発生時の適切な処理をすることに対し心配する必要がなくなります。以下の依存関係をpom.xmlの<dependencies>セクションに追加します。

これで、プールされたデータベース接続を自動的に管理されるJTAトランザクションに登録する方法をプロジェクトは認識します(必要であれば、コンポーネントを追加することでTransactionManagerをコードに直接入れることもできます)。

Helidon’s Managed JPA Support

次のコンポーネントはHelidon MPのJPAサポートです。これはJPAプロバイダを接続プールやトランザクションマネージャとバインドする glue code (糊の役目を果たすコード)です。上述のコンポーネント実装を追加済みなので、Helidon MPのJPAサポートをこれらの上に追加できます。以下の依存関係をpom.xmlの<dependencies>セクションに追加します。

これでプロジェクトがEntityManagerのようなJPAコンストラクトを認識できますが、まだこれらの実装を提供するJPAプロバイダを選択していません。

JPA Provider

察しが付いていると思いますが、ランタイムパズルの最後のコンポーネントはJPAプロバイダです。現在以下のJPAプロバイダをサポートしています。

この例では、EclipseLinkを使います。以下の依存関係をpom.xmlの<dependencies>セクションに追加します。

この設定で、プロジェクトがEclipseLinkを使ってEntityManagerオブジェクトのようなものを実装します。

Helidon MPのEclipseLink統合は、EclipseLinkに自身がこれまでのJava EEアプリケーションサーバで稼働しているように思わせている点で、ちょっと不自然です(もちろんそうではないのですが)。そのため、この種の環境でJPAを使用して実行できるほとんどすべてのことは、この単純なマイクロサービス指向の環境で実行できます。

ベースとなるHelidon MPサーバを強化するランタイムコンポーネントを追加してきた結果、Helidon MPはJPAを認識します。以後では、実際にJPAサポートを使うコードを記述、コンパイルするためのいくつかのAPIを追加する必要があります。

JPA and JTA APIs

まず、プロジェクトがプロバイダ非依存のJPA APIとJTA APIを認識することを確認する必要があります。上記APIを実装するランタイムコンポーネントを追加しましたが、EntityManagerのようなものを使うコードをこの時点で書こうとすると、コンパイルが通らないでしょう。うまくいくようにするには、以下の依存関係をpom.xmlの<dependencies>セクションに追加する必要があります。

これで javax.persistence.EntityManager や javax.transaction.Transactional といったものを参照するコードを記述、コンパイルできるようになりました。

javax.persistence.EntityManager
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/EntityManager.html
javax.transaction.Transactional
https://jakarta.ee/specifications/transactions/1.3/apidocs/javax/transaction/Transactional.html

注意いただきたいのは、この場合のMavenのスコープは provided である点です。

  • EclipseLink:JPA APIの実装を実行時に利用可能にする
  • Helidon MPのNarayana統合(先ほど追加しました):JTA APIの実装を実行時に利用可能にする

ここでは、コンパイル時にこれらのAPIを利用可能にしますが、ランタイムコンポーネントが実行時にこれらのAPIを提供することをシステムに伝えています。

Dependency Scope
http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope

Configuring The Project

これでプロジェクトのセットアップができたので、ここからはインストールしたコンポーネントがJPAプロジェクトの背後にあるデータベースにアクセスする方法を認識するよう、構成していきましょう。

Creating Tables

まずやることは、アプリケーションがデータベースにアクセスするときに表の中に構造化データがあるよう、データベースに表を作成するためのDDLスクリプトを作成します。JPA自身はこれに対応する機能を提供していますが、今回の例では使わずに、シンプルかつ透過的にしておきます。

以下の内容のsrc/main/resources/greeting.ddlというファイルを作成します。

H2データベースに対して実行された場合、このスクリプトで2つの列(SALUTATIONおよびRESPONSE)を持つGREETINGという名前のシンプルな表を作成します。また、この表に少なくとも1つの行があるようにします。(IF NOT EXISTSやMERGE INTOのようなH2コンストラクトにより、このDDLが失敗することなく、また作成済みのスキーマを損傷することなく複数回実行できます。)

IF NOT EXISTS
https://www.h2database.com/html/commands.html?highlight=createTable&search=create#create_table
MERGE INTO
https://www.h2database.com/html/commands.html#merge_into

最後に、実際のアプリケーションにおけるデータベースのスキーマ・アップグレードの管理は、多くの記事や本のテーマになり得る厄介な主題であり、この例のような単純なDDLファイルの使用は本番環境ではまったく適していないことにご注意ください(ただし、チュートリアル形式のブログ記事ではこの方法を利用するのが簡単です)。

Configuring the HikariCP Connection Pool

Helidon MPはその設定がどこから来ているかを気にしません。つまり、アプリケーションへ設定を仕込むほぼ無数のやり方があります。簡単のため、Helidonがデフォルトで認識する構成ソースの一つ、application.yamlを使うことにします。src/main/resources/application.yamlというファイルを作成し、以下の内容を書き込みます。

Configuring the Server
https://helidon.io/docs/latest/#/microprofile/02_server-configuration

(先頭の2行はWebサーバに関連するものであり、Helidon MPのマネージドJPAサポートとは関係しません。この設定はWebアプリケーションをtcpポート番号8080にバインドします)。

YAMLのjavax/sql/DataSourceの配下の設定で、接続プールに対し、当然ながらjavax.sql.DataSourceインスタンスの作成方法を伝えています。今回は、greetingDataSourceという名前のjavax.sql.DataSourceインスタンスの作成方法を示すレシピがあります。

その下の情報はHikariCP接続プールのルールに従います。この情報は以下のURLから確認できます。

Configuration (knobs, baby!)
https://github.com/brettwooldridge/HikariCP/blob/dev/README.md#configuration-knobs-baby

簡単に言うと、以下を設定します。

属性説明設定項目
dataSourceClassName基盤として使うデータベースドライバが提供するDataSourceクラス名org.h2.jdbcx.JdbcDataSource
dataSourceデータソースに指定するプロパティurl
user
password

urlプロパティを例にすると、JDBC準拠のURLプロパティはH2データベースのルールに従います。その情報は以下のURLから確認できます。

In-Memory Databases
http://www.h2database.com/html/features.html#in_memory_databases

ここでは、greeting (:greeting)という名前のインメモリのH2データベース(jdbc:h2:mem)が必要です。データベースが起動したときに上記で作成したDDLスクリプトを実行します。

Execute SQL on Connection
http://www.h2database.com/html/features.html#execute_sql_on_connection

Configuring JPA

データベース接続のプール方法、およびjavax.sql.DataSourceインスタンスとしてHelidon MP内でデータベース接続プールが表す方法を接続プールに伝えたので、接続プールの発見方法と利用方法、Javaエンティティクラスと連携する方法(少々後述します)をJPAに対して伝えます。

JPAはMETA-INF/persistence.xmlという名前のクラスパスリソースを探して、その挙動を理解しようとします。このファイルは複数の異なる形式を取り得るため、Java開発者の混乱の元凶となることが多々あります。

最初の形式は、EntityManagerやトランザクション、スレッドの安全性、例外処理、その他の迷惑な懸念事項を管理したい開発者向けです(今回はこちらではありません)。persistence.xmlでデータベース接続を直接指定し、種々のJTA指向のコンストラクトの利用を回避する必要があります。Helidon MPのJPAサポートはこの形式を使いません。ここで述べたのは、Webで見つかる悪いJPAの例で出てくるからに過ぎません。

もう一つの形式は、Helidon MPのマネージドJPAサポートのような、マネージドJPA実装を使いたい開発者向けです(つまりこちらです)。ここでは、JTAの使用方法を指定し、データベースへの接続方法に関する懸念は接続プール実装(先ほど設定したもの)に振ります。Helidon MPのマネージドJPAサポートはこの形式を使用します。

Helidon MPが提供するようなマネージドJPA環境では、基本的には、JTAトランザクションに登録可能な名前付きデータソースの場所をJPAに知らせればよいのです。次に、名前付きデータソースはデータベースとの対話方法を心配しますが、Java EEの場合、以前はこの名前はJNDI名でした(java:comp/env/jdbcで始まることが多い)が、Helidon MPの場合はもっとシンプルです。構成したデータソースを識別できる限り、どんな名前でもかまいません。

重要な点として、データベース接続に関するJPAの詳細を伝えないでください。application.yamlファイルを編集したときにすでに行っているため、ここではそうせずに、JPAに対し以前構成したデータソースの名前を渡し、ランタイムが見つけられるようにします。

以前構成したgreetingDataSourceを覚えていますか?それがデータソース名です。

それでは、src/main/resources/META-INF/persistence.xmlというファイルを作成し、以下の内容を書き込みます。

このファイルの大部分はXMLのボイラープレートであり、実際の内容はかなり簡単です。

  • ここで設定しているpersistence-unitはgreetingという名前で、トランザクション管理にJTAを使用する
  • <jta-data-source>要素はJPAが利用するデータソース名が、このエントリですでに構成済みのgreetingDataSourceであることを示している。ここでJPAと接続プール間のリンクを作成する。
  • <class>要素には、どのJavaクラスがJPAが認識すべきJPAエンティティであるかを指定する(後ほど説明)。
  • <properties>節を使い、JPA実装をベンダー固有のプロパティで設定する。今回の例では、以下のようなEclipseLinkに起動時に何を期待できるかについてのヒントを提供している。なお、EclipseLinkがHelidonのマネージドJPAサポートで適切に機能するために必要なEclipseLink固有のプロパティはeclipselink.weavingプロパティのみで、falseに設定する必要がある。後ほど(ビルド時の)組み上げ方を説明する。
    • 早期にデプロイする必要がある
    • H2データベースをターゲットにしている
    • Javaのネイティブロギング機能を使用してログを記録する、など

deploy-on-startup
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#delayonstartup
target-database
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#target-database
logging.logger
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#sthref1021

Persistence Property Extensions Reference
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm
weaving
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#weaving

最後に、実際のアプリケーションのどこにMETA-INF/persistence.xmlを配置すべきかという現実的な懸念があります。例えば以下のような懸念です。

  • JPAエンティティと同じプロジェクトに参加する必要があるか
  • 複数のステージング環境がある場合はどうなるのか
  • 単体テストの場合はどうなのか

このエントリでは、これらの問題については意図的に扱いません。

Configuring Logging

Helidon MPはJavaのネイティブログ機能を利用します。便宜上、先に進んでsrc /main/resources/logging.propertiesを追加し、次の内容を入力します。

アプリケーションを開始すると、この設定で接続プールの動作やJAX-RS機構の動作などの有用な情報を確認できます。Helidon MPはこれらの情報を自動的に収集します。

Writing Code

データベース接続プールの構成とJPAがデータベース接続プールを認識できるようになり、全てのランタイムコンポーネントとAPIコンポーネントの構成が完了したので、コードを書いていきましょう。

The Entity Class

まず、JPAエンティティクラスです。src/main/java/io/helidon/example/jpa/Greeting.javaというファイルを作成します。

これはJPAエンティティクラスです。最大の透明性を確保するために、多くの場合暗黙のデフォルトをすべて明示的に設定しました。

これにより、このクラスがエンティティを定義し、そのエンティティのJPA関連の状態へJPAプロバイダーがそのフィールドから(例えば、getterやsetterからではなく)直接アクセスすることが期待できます。

Annotation Type Entity
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/Entity.html
Annotation Type Access
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/Access.html

今回はGREETING表にマップします。GREETING表のidはsalutationフィールドです(上記のDDLスクリプトを作成したときのSALUTATION列を思い出すかもしれません。salutationフィールドがそれにマップされることがわかります)。

Annotation Type Table
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/Table.html
Annotation Type Id
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/Id.html
Annotation Type Column
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/Column.html

エンティティの設計に関するさまざまな問題への対応はこの記事の範囲を超えているため、ここまでにします。

A Brief Digression On Build-Time Weaving

JPAの構成について説明した際に上記のeclipselink.weavingプロパティについて言及したこと、falseに設定する必要があることを思い出してください。その理由は以下のとおりです。

weaving
https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#weaving

EclipseLinkは全てのJPAプロバイダと同様、(他にも理由がありますが)EclipseLinkがエンティティクラスのどのフィールドに設定したかを透過的にユーザーにわかるようにするため、エンティティクラスのバイトコードを変更できることを期待しています。そうでなければ、この変更を自身で手作業で追跡しなければならず、全くもってつまらないことです。このバイトコード変更がweavingとして知られています。

Weavingをビルド時に静的に実施できますし、実行時に動的に実行することもできますが、要点を述べると、Helidon MPは複数のクラスローダーとデプロイメントフェーズを備えた重量級のJava EEアプリケーションサーバではないため、JPA仕様で記述されている方法での動的な実行時のweavingは実際には不可能です。

幸いにも、ビルド時のweavingは常にやりたいことです。多くの理由で パフォーマンスからクラスロードの問題に至るまでの多くの理由で、とにかく常にやりたいことです。それゆえ、ビルド時のweavingを構成するために少々pom.xmlに追加する必要があります。

ここで、<plugin>を追加しています。これを使ってEclipseLinkに対しビルド時にweavingを実行することを指示しています(この節は静的なJPAメタモデルクラスも実行時に作成します)。

Inject a Container-Managed EntityManager

プロジェクトのセットアップ、データベースの準備、設定はすべて完了しました。ビルド時にコンパイルおよび編成されるJPAエンティティができました。いよいよ、これらすべてを使い始めます。

以下のimportを含めるよう、src/main/java/io/helidon/example/jpa/ExampleResource.javaを変更します。

次いで、リソースが発見、処理されるようにscopeアノテーションで注釈を付けます。

次に、挿入されたEntityManagerを追加します。Java EEの場合と同様、@PersistenceContextアノテーションを使います。

これまでJava EEを使ってらっしゃったのであれば、簡単で、慣れていますよね。

それではJPA関連のリソースメソッドを追加します。

ここで@Transactionalの利用に注目してください。これにより、このメソッド実行前にJTAトランザクションが自動的に開始され、最後にコミットされます。問題が発生すれば、トランザクションはロールバックされます。注入されたEntityManagerが自動的に参加します。

Annotation Type Transactional
https://jakarta.ee/specifications/transactions/1.3/apidocs/javax/transaction/Transactional.html

Building, Running and Vetting the Application

データベースの構成、接続プールの構成、JPAに接続プールへの対話方法の伝達、エンティティの作成とコードの作成が完了しました。ではアプリケーションをビルドしましょう。cdでトップレベルのディレクトリに移動し、mvn packageを実行します。

ここまでで問題がなければ、アプリケーションを java -jar target/helidon-jpa.jar で実行できるはずです。

最後に、curl http://localhost:8080/example/response/Marco と入力することで、すべての要素が連携して動作することをテストできます。レスポンスとしてPoloが返るはずです。

Summary and Takeaway

Helidon MPのJPAサポートは通常の表面レベルの統合にとどまらず、Java EEアプリケーションサーバ時代から慣れ親しんだタイプの管理サポートを提供します。これは透過的に、使い慣れたツールを使用して実行します。

この記事を楽しんで頂けましたでしょうか。Helidon MPのマネージドJPAサポートの高度な機能について今後取り上げる予定です。

Helidonの詳細については、メインプロジェクトサイトとGithubリポジトリをご覧ください。

Helidon Project
https://helidon.io
GitHub Repository
https://github.com/oracle/helidon

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中