ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Java Persistence API (JPA) 実践入門

この記事は Java EE Advent Calendar 2013 の 24 日目の記事です。 昨日は nabedge さんの 「JAX-RSのビューとしてMixer2を使ってみる」 でした。 明日は eller86 さんの 「ウェブアプリケーションサーバでよくあるクラスローダのトラブル」 です!

概要

これから Java Persistence API (JPA) を使ってみようとしている人向けの、JPA の簡単な使い方や IDE の機能の紹介などの話です。 私自身、最近 JPA を使い始めたのですが、日本語の実践的な入門記事みたいなのが見当たらなくて苦労した (JPA の概念の説明記事は結構あって助かったのですが) ので、少しでも参考になればと思います。 IDE 関係の話は Eclipse ユーザー向けの説明になりますが、NetBeans でもだいたい似たようなことができるはずです。 JPA 実装としては、参照実装である EclipseLink を使用しています。

予めて断っておきますが、わりと手探りで方法を探したりしてるのでもっといい方法があったりするかもしれません。

ちなみにこの記事は Java EE Advent Calendar の記事ではありますが、JPAJava EE だけでなく Java SE 環境でも使用できます。

JPA の概念などの入門記事

JPA の概念の説明などの入門記事はわかりやすいものがいくつかありますので、概念的な部分は他の記事を見ていただければよいかと思います。 特に次の記事が非常に参考になりました。

また、英語ですが OracleJava EEチュートリアルも参考になります。

JPA 仕様

JPA 仕様は JSR として公開されています。 正確な記述を知りたい時などは JSR を参照しましょう。

開発環境

この記事では、次のような開発環境を念頭において話をします。

まずは簡単に JPA を使ってみる

JPA を使用した簡単なサンプルプロジェクトを作ってみました。

次のように git clone して、gradle run で実行できます。 (JDK 1.7 がインストールされていれば動くはずです。)

git clone https://github.com/nobuoka/jpa-sample.git
cd jpa-sample
./gradlew run

サンプルプロジェクトの説明

このサンプルプロジェクトには、Entity クラスとして、次の User クラスのみが使含まれています。 User クラスは persistence.xml で定義される 「jpa-sample」 という persistence unit に含まれています。

この User を 3 つ作成して DB に保存し、その後 DB から全 User を取得して表示するという処理を次のクラスで行なっています。

接続先 DB などの設定は psersistence.xml に記述することもできますが、今回は createEntityManagerFactory メソッドの第 2 引数に渡すようにしています (App.createDbSetting メソッド参照)。 DBMS は Derby を指定しており、DB が存在しない場合は自動的に DB が作成されます (app/jpa-sample.derby というディレクトリ)。 また、次の設定により、テーブルが存在しない場合はテーブルも生成されるようになっています。

settings.put(PersistenceUnitProperties.DDL_GENERATION, "create-tables");

今回の例では、JPA 実装として EclipseLink を使用し、接続先 DB の DBMS が Derby であるため、依存ライブラリとして EclipseLink と Derby の JDBC を指定してあります。

そんな感じで

上で説明したように、とりあえず JPA を使ってみるだけなら次のものを用意すれば使用できます。

  • 依存ライブラリを記述 (JPA 実装と接続先 DB 用の JDBC ドライバ)
  • Entity クラス (@Entity アノテーションを付与したクラス) を記述
  • persistence.xml (Java SE 環境で使用する場合は META-INF/persistence.xml に置く) を記述

あとは EntityManager を取得して使用するだけです。 あ、もちろん接続先 DB の準備も必要です。 上の例では Derby を使って、接続時に DB の作成やテーブルの作成もしてしまっているので手で準備はしていませんが、一般的には *1 予め DB を用意しておく必要があります。

クエリを投げる方法

上のサンプルプロジェクトでは、User クラスで定義された 「User.findAll」 という名前の Named Query を使用して全 User を取得するクエリを投げていました。

JPA では、クエリの記述方法として次のようなものがあります。

  • Java Persistence query language (JPQL) : JPA 用に定義されたクエリ言語で、SQL などに変換されて使用される
    • クエリに名前をつけて Entity クラスのアノテーションなどで定義することもできる (named query)
  • Native SQL : 生の SQL をクエリとして使用することもできる
  • Criteria API : 上のような文字列ベースのクエリではなく、オブジェクトベースでクエリを生成する

可読性やパフォーマンスを考慮すると、静的なクエリには named query を使うのが良さそうですね。 動的にクエリを組み立てる場合は、Criteria API と後述する Metamodel クラスを組み合わせて使うのが型安全性が高くて良さそうです。

パフォーマンス的な話については、去年の Java EE Advent Calendar の次の記事が興味深いです。

Metamodel クラスの生成

JPA 2.0 で Metamodel というものが導入されました。 Metamodel クラスは、Entity の情報を持っているもので、例えば永続フィールドの型の情報を Metamodel クラスが持っていたりします。 Metamodel クラスと Criteria API を組み合わせて使うことで、型の安全性の高い状態でクエリ生成を行うことができます。

Metamodel 自体は動的に生成されることもあるものですが、コーディング時に使用するためには静的なクラスとして Metamodel クラス (特に、実装間の互換性が保証された canonical metamodel クラス) を生成する必要があります。 静的な Metamodel クラスを生成するために Annotation Processing を使用します。

EclipseLink の Annotation Processor で Metamodel クラスを生成する手順は次のドキュメントに記述されています。

Gradle を使用している場合は、次のようにタスクを定義して、コンパイル前に Annotation Processing を実行させることができます。

この例では、生成されたクラスは gen/main/java ディレクトリ下に置くようにしています *2。 上のような Gradle タスクの問題は、生成された Metamodel クラスの元の Entity が削除された場合に Metamodel クラスを手で削除しないといけない (か、または gen/main/java ディレクトリをまるごと削除する必要がある) ということですが、まあとりあえずはそれでいいかなぁという気でいます。

Metamodel クラスを Criteria API で使用するサンプルコードは次のようになります。 単純に、"testuser1" という nameUser 一覧を取得しているのですが、JPQL と比べるとやや可読性は低くなってしまいますね。

Metamodel については以前に書いた記事もあわせて参考にしてください。

EclipseJPA

ここからは EclipseJPA を扱うための話をします。

Eclipse へのインポート

上のサンプルプロジェクトを Eclipse で使用することを考えます。 まずは Eclipse にインポートします。 Gradle を扱うために、Eclipse にあらかじめ 「Gradle Integration for Eclipseプラグインがインストールされている必要があります。 (「Help」 メニューの 「Eclipse Marketplace」 からインストールできます。)

Eclipse の 「File」 メニューから 「Import...」 を選択し、「Gradle」 の 「Gradle Project」 を選びます。 プロジェクトのディレクトリを選択し、「Build Model」 を実行し、表示されたプロジェクトをすべて選んで 「Finish」。

これで Gradle プロジェクトとして上のサンプルプロジェクトを Eclipse にインポートできました。 Eclipse にはサブプロジェクトの概念がないので、Gradle プロジェクトのサブプロジェクトもルートプロジェクトも、Eclipse 上では同じ階層のプロジェクトとなります。

JPA プロジェクトに変換する

インポートした直後のプロジェクトは単なる Gradle プロジェクトです。 JPA 関係の便利機能が使えるように、JPA プロジェクトにコンバートします。

Eclipse の 「Project Explorer」 内でプロジェクト (上のサンプルプロジェクトであれば 「data-model」 プロジェクト) を右クリックして、「Configure」 の 「Convert JPA Project...」 を選択してください。

が選択されていることを確認して次へ。 JPA Facet の設定入力欄では、「Platform」 を 「Generic 2.1」 にしておけば良さそうです (あまり違いがわかってない; 「Generic 2.1」 だと Entity を元にテーブルを生成することができないが、「EclipseLink 2.5.x」 だとそれができる、というような違いがある)。 「JPA implementation」 はとりあえず 「Disable Library Configuration」 で良さそうです ((何に使われるかちゃんとわかってない。))。

「Connection」 はとりあえず 「<None>」 にしておきます。 (後で接続します。)

これで JPA プロジェクトに変換できました。

JPA パースペクティブを開く

Eclipse には、データベース接続などの情報が表示される JPA パースペクティブがあります。 デフォルトでは Eclipse ウィンドウの右上の方にパースペクティブの変更ボタンがあるはずで、そこから JPA パースペクティブに切り替えられます。

f:id:nobuoka:20131223172752p:plain

まだデータベース接続をしていないので何も情報を得られませんが、例えば 「Data Source Explorer」 でデータベースの情報を見たりすることができます。 このあとで、DB 接続を行なっていきます。

ドライバ定義の追加 (例として PostgreSQL 9.3 のドライバを追加する)

Eclipse 上で DB の情報を取得したりするために必要な、DB 接続に用いるドライバの定義を追加します。 今回は例として PostgreSQL 9.3.2 用のドライバを追加します。 次のページから 「JDBC41 Postgresql Driver, Version 9.3-1100」 をダウンロードしておきます。

Eclipse の 「Window」 メニューから 「Preferences」 を選び、「Data Management」 の 「Connectivity」 の 「Driver Definitions」 を開きます。 「Add」 して 「New Driver Definition」 を開きます。 「Name/Type」 タグにおいて、「Generic JDBC Driver」 を選択 *3 して、「Name」 に 「PostgreSQL 9.3 Driver」 とでも入れておきます。 「JAR List」 では先ほどダウンロードした JDBC の JAR ファイルを選択します。 最後に Properties として Connection URL などを適当に入力します (実際の値はあとで入力するため、ここではサンプルの値を入れておけば良い)。 Driver Class だけはちゃんと次に示す値を入力する必要があります。

この操作は、接続先 DBMS の種類ごとに 1 回のみ必要なものです。

DB 接続の追加

新たに DB 接続を追加します。 JPA パースペクティブの 「Data Source Explorer」 の 「Database Connections」 を右クリックして、「New」 を選びます。

「New Connection Profile」 というウィンドウが開くので、「Generic JDBC」 を選択し、「Name」 は適当に DB を表す名前 (今回の例では 「Sample DB」 としておく) を入力して次へ。 ドライバの設定では、先ほど定義した 「PostgreSQL 9.3 Driver」 を選んで、プロパティとして今回の DB の接続に必要な値を入力します。

  • Connection URL : jdbc:postgresql://localhost/sample_db
  • Database Name : sample_db
  • User ID : sample_user_id
  • Password : sample_password

で、「Finish」。

DB 接続が追加されたので、「Data Source Explorer」 で接続先 DB のスキーマなどを確認できます。

JPA プロジェクトに DB 接続を追加する

プロジェクトを右クリックして 「Properties」 をクリックして設定画面を開き、「JPA」 の項目を選択します。 「Connection」 の選択肢に、先ほど追加した DB 接続 (今回の例だと 「Sample DB」) が追加されていますので、それを選びます。

「Connection」 の設定として、「Override default catalog from connection」 や 「Override default schema from connection」 というものがありますが、PostgreSQL 9.3 に接続する今回の場合はどちらも有効にする必要があります (理由はよくわかってませんが、Generic JDBC を使って PostgreSQL に接続しているから?)。 どちらも有効にして、それぞれ 「"sample_db"」 と 「"public"」 を選択してください。

既存のテーブルから Entity クラスを生成する

Eclipse の 「Project Explorer」 を右クリックして 「JPA Tools」 の 「Generate Entities from Tables...」 を選んで、既存のテーブルから Entity クラスを生成することができます。 既存の DB に接続するために Entity を生成する際に便利ですね。 既存のテーブルから Entity を生成するためには、上で説明したように JPA プロジェクトに DB 接続を追加しておく必要があります。

Java EE っぽい話

せっかくの Java EE Advent Calendar なので、Java EE での JPA の話にも少し触れておきます。

Java EE コンテナにおける entity manager factory の取得方法

JSR 338 (JPA 2.1) の 7.3.1 節に記述されています。

Java EE 環境では、PersistenceUnit アノテーションを用いて、EntityManagerFactory をインジェクションさせることができます。 また、 JNDI ルックアップを通して EntityManagerFactory を取得することもできます。

@PersistenceUnit
EntityManagerFactory emf;

コンテナによって管理された entity manager の取得

また、Java EE 環境であれば、EntityManagerFactory とやりとりせず、コンテナによって管理される entity manager を Dependency Injection (DI) で取得することもできます *4。 JSR 338 (JPA 2.1) 7.2.1 節に記述されています。

@PersistenceContext
EntityManager em;

@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager orderEM;

永続ユニット (Persistence Unit) のパッケージング

JSR 338 (JPA 2.1) の 8 章あたりの話です。

Java EE 環境では、EJB-JAR、WAR、EAR、そしてアプリケーションクライアント JAR が永続ユニットを定義できます。 それらの範囲で、任意の個数の永続ユニットを定義することができます。

永続ユニットは、WAR や EAR の中の 1 個以上の jar ファイルにパッケージングされたり、EJB-JAR ファイルの中のクラスの集合としてパッケージングされたり、WAR の classes ディレクトリのクラスの集合としてパッケージされたりします。 それらの組み合わせもあり得ます。

永続ユニットは persistence.xml ファイルで定義されるわけですが、persistence.xml ファイルを含む META-INF ディレクトリの親ディレクトリ、あるいはそのような META-INF ディレクトリを含む jar ファイルは永続ユニットのルートです。 Java EE 環境における永続ユニットのルートは、次のどれかでなければなりません:

  • EJB-JAR ファイル
  • WAR ファイルの WEB-INF/classes ディレクト
  • WAR ファイルの WEB-INF/lib ディレクトリ内の jar ファイル
  • EAR ライブラリディレクトリ内の jar ファイル
  • アプリケーションクライアント jar ファイル

Java EE 関係の話について

ここら辺は JSR 338 を 「ふむふむなるほど」 って読みながら知った知識程度しかないので、とりあえず参考程度に。

おわりに

JPA を使い始めてから得られた知識などをまとめてみました。 2 ヶ月ぐらい前に書いた 「Java での web アプリケーション開発時の開発環境メモ #1」 という記事で 「DB 周りどうしたらいいのかよくわかんない」 って書いたら、ブコメで 『DB周りはJPA2.0でOK。今のJavaEEは「迷ったら標準」』 って教えてもらって JPA を学び始めたのですが、web 上の情報だけだとある程度使えるようになるまでわりと苦労しました。 わかってしまえば (とりあえず使う分には) そんなに難しくもないんですけどね。

まだまだ使いこなすところまで行っていませんが、JPA はなかなか使いやすくて良いと思っています。

*1:PostgreSQL を使う場合など

*2:どこに置くようにするのがいいんでしょうね

*3:PostgreSQL JDBC Driver」 というものもありますが、バージョン 8.x にしか対応していないようなので、Generic の方を選びます。

*4:あと JNDI ルックアップを通しても可能です