最終更新日:2020/03/21 原本2010-04-27

今からでも遅くない JPAを学ぼう!(前編)
O/Rマッピングフレームワークへの招待

O/Rマッパーを体験しましょう

ダウンロード サンプルコード (5.3 KB)

 Java Persistence API(JPA)を使ってオブジェクトの世界とリレーショナルの世界を結び付ける方法を一緒に学んでいきたいと考えています。前編ではJavaアプリケーションの簡単なサンプルアプリケーションを作成しつつ説明します。

JPAとは

 JPA(Java Persistence API)とはオブジェクトの世界からリレーショナルの世界へ、あるいはその逆への変換を行うためのAPIです。

 それでは何もJPAを使わずともHibernateやiBatisを既に使っているから必要ないのではと考えられた方も多いかと思います。確かに既にそれらのO/Rマッピングフレームワーク(以降、O/Rマッパー)を利用されているのであれば特に必要ないのかもしれません。

 そう思った方も少し待ってください。データベース製品の多様性を隠ぺいするためにJDBCが考えられたように、あるいはMOM製品の多様性を隠ぺいするためにJMSというAPIが考えられました。ところがO/Rマッパーの違いを隠ぺいするためのAPIは存在しなかったのです。iBatisを使用されている方にはあまり嬉しくないかもしれませんが、JPAの仕様作成の中心人物こそHibernateプロジェクトの創始者であるGavin King氏であり、Hibernateの血を色濃く受け継いでいます。

JDBCを使ってもO/Rマッピング

 直接JDBCを使っても、それも立派なO/Rマッピングです。筆者はこの方法が駄目だと主張するつもりは毛頭ありません。テーブル間の関連が非常に複雑である場合、JDBCのAPIを使って記述した方が楽な場合もあります。筆者が参画していたプロジェクトでもコストをかけないという制約のもとBI(Business Intelligence)に似たシステムを構築したことがありますが、このような場合、SQLを自由に駆使できるJDBCを使った方がO/Rマッパーを使うよりもむしろ楽だと感じました。

O/Rマッパーが必要な理由

 この理由は、既に言いつくされた感があるため言及する必要がないと感じています。それはオブジェクトモデルとリレーショナルモデル間のマッピングが難しいことにあります。インピーダンス・ミスマッチという言葉がよくつかわれますが、これを解消するための試みがHibernateなどのO/Rマッパーです。やはりプログラムの中にSQL文が混じりこまず、可読性が高まるということもあり、保守性も優れているという理由から支持されているのだと思います。プログラムをよりオブジェクト指向化するためのフレームワークを提供してくれるO/Rマッパーは非常に有難い存在と言えます。

よくある誤解

 O/RマッパーさえあればRDBの知識は必要ないと思っている方が多いようですが、コーディングの際にはRDBを意識する必要があります。意識しつつもSQLを直接書く必要がないだけの話です。ある程度RDBやSQLを知っていることがO/Rマッパーを使用するための前提であるとも言えます。常に過度な期待は禁物です。

JPAを使っての実装

実装のための準備

 前節の冒頭でも書きましたが、JPA自体はO/Rマッパーを隠ぺいするため仕様です。隠ぺいという言葉が否定的に聞こえるのであれば、O/Rマッパーを意識せずにO/Rマッピングを行うための仕様であると言えます。

 とは言え、環境を構築する場合にはO/Rマッパーを意識せざるを得ません。今回はGlassFishにバンドルされているTopLink EssentialsというO/Rマッパーを使用します。バンドルされているため、GlassFishをインストールすればTopLink Essentialsもインストールされます。開発環境としてはNetBeansを使用します。GlassFishとNetBeansのインストール方法については「GlassFishとNetBeansのインストール」を参考にしていただければと思います。

 ちなみにJBossにはHibernateがバンドルされています。バンドルされているWebアプリケーションサーバであれば準備はこれだけです。データベースはMySQLを使用します。インストールについては「Webアプリケーションの作成 GlassFish管理コンソールを使う」を、操作の仕方については「Webアプリケーションの作成 MySQL Workbenchを使う」を参照ください。

 一からデータベースを構築することは難しいため、当記事のトップにデータベース構築サンプルとして.sqlファイルを用意しています。MySQL Workbenchでのインポートの仕方については「Webアプリケーションの作成 JDBCレルムで認証実現」の4ページ目で解説しています。ページトップに「サンプルコード」のsqlファイルとして置いています。MySQL Workbenchでimportしてデータベースの構築を行ってください。

NetBeansを使っての実装

プロジェクトの作成

 前編ではJavaアプリケーションを使って JPA を実装します。

 NetBeansで[ファイル]-[新規プロジェクト]をクリックすると[新規プロジェクト]画面が開きます。右ペインに[プロジェクトを選択]があり、その下に[カテゴリ]があるので[Java]を選択します。その右側にある[プロジェクト]の[Javaアプリケーション]を選択し[次へ]ボタンをクリックします。

 [新規プロジェクト]画面はそのままで、右ペインが[名前と場所]に変っているはずです。[プロジェクト名]は何でも構いませんが、今回は「JPASample01」と入力します。[主クラスを作成]のチェックボックスにチェックを入れ、主クラス名にはパッケージ名を含んだクラス名を入力します。ここでは「ja.kawakubo.JPAStandalone」と入力します。パッケージ名は筆者のドメイン名であるため、適宜変更してください。

 [完了]ボタンをクリックすると右ペインにJPAStandaloneクラスがエディタで開きます。

JPAのプレイヤーの紹介

 いきなりJavaアプリケーションを作成しても構いませんが、やはり基本となるプレイヤーは整理する必要があります。表1がJPAの主要なプレイヤーです。後編では裏方さんも紹介します。

表1.JPAプレイヤーズ一覧
JPAのプレイヤー 役割
Entityクラス Entityという言葉は多用されますが一般的にRDBのテーブルを表します。それをオブジェクトモデルとして表現したものがEntityクラスです。この時点でO/Rマッピングの中心となるのがEntityクラスであることが分かります。
Entityオブジェクト Entityに対し、Entityインスタンスはテーブルの1行を表します。それをオブジェクトモデルとして表現したものがEntityオブジェクトです。つまりEntityクラスをインスタンス化したものを当記事ではEntityオブジェクトと呼ぶことにします。
Entityマネージャ Entityオブジェクトを使い、実際のRDBに対しCRUD操作を行います。
Entityマネージャファクトリ 文字通り、Entityマネージャを生成する役割を持っています。

Entityクラスの要件

 Entityクラスは以下に従ってコーディングする必要があります。

  • javax.persistence.Entityアノテーションによりクラスをアノテーションしなくてはならない
  • publicまたはprotectedの引数のないコンストラクタを持っていなくてはならない。ただし、その他のコンストラクタも持つことはできる
  • クラス、メソッド、永続性フィールド(RDBのテーブルのカラムに相当するフィールド)をfinal宣言してはならない
  • EntityオブジェクトはEntityマネージャによりRDBのCRUD操作と結び付けられている状態(MANAGED状態)と結び付けられていない状態(DETACHED状態)があるが、DETACHED状態の値として渡される場合、Serializableインターフェースを実装しなければならない
  • Entityクラスでないクラスを継承する場合、その継承されるクラスはEntityクラスである必要がある
  • 永続性インスタンス変数は、private、protected、package-privateのいずれかで宣言されていなければならず、Entityクラスのメソッドを介してのみアクセスできる

 以上の規則に従ってEntityクラスを作成します。データベースは「実装のための準備」で構築したimoshopというスキーマを使用します。その中にmeigaraテーブルが存在しています。そのmeigaraテーブルを表すMeigara.javaというEntityクラスを作成します。

 クラスを作成するにはNetBeansの左ペインから[プロジェクト]タブを選択します。JPASample01プロジェクトを右クリックし、[新規]-[Javaクラス]を選択すると[新規Javaクラス]画面が表示されます。[クラス名]に「Meigara」、[パッケージ名]に「jp.kawakubo」と入力し[完了]ボタンをクリックするとMeigaraクラスを作成するエディタが右ペインに表示されます。

 ただし、このままではJPA関連のクラスが存在しないため、TopLink Essentialライブラリをプロジェクトに追加する必要があります。NetBeansの左ペインで[プロジェクト]-[JPASample01]-[ライブラリ]を選択し、右クリックします。[プロパティー]をクリックすると[プロジェクトプロパティー]画面が表示されるので、右ペインの[コンパイル]タブを開き、右側の[ライブラリを追加]ボタンをクリックします。[ライブラリを追加]画面が表示され、[大域ライブラリ]の下に追加すべきライブラリの候補が並んでおり、下の方にスクロールすると[TopLink Essentials]があるはずです。それを選択し、[ライブラリを追加]ボタンをクリックすると、元の[プロジェクトプロパティー]画面に戻ります。[了解]ボタンをクリックすると、プロジェクトのライブラリにtoplink-essential.jarとtoplink-essentials-agent.jarが追加されていればOKです。

 これでjavax.persistence.*のクラスが使用できるようになります。また、MySQLのドライバを追加する必要があります。上記と同様の手順で[大域ライブラリ]から[MySQL JDBCドライバ]を選択してライブラリを追加してください。

NetBeansを使ってMeigaraクラスを作成

 NetBeansでMeigaraクラスを開きます。まずは図1のようにmeigaraテーブルのカラムに相当するフィールドを宣言します。次にこれらのフィールドのgetter/setterメソッドを作成します。

 NetBeansの場合、[リファクタリング]-[フィールドをカプセル化]をクリックすると、[フィールドをカプセル化]画面が表示されます。右側にある[全てを選択]ボタンをクリックし、さらに[リファクタリング]をクリックするとsetter/getterメソッドが作成されます。

図1.フィールド変数の宣言
図1.フィールド変数の宣言

 ここからEntityクラス特有のコーディングをします。最初は、class宣言の前にEntityアノテーションとTableアノテーションを記述します。javax.persistence.Entityクラスとjavax.persistence.Tableクラスもimportします。

補足:面倒な作業は開発環境に任せる

 アノテーションを記述するたびにimport文を書くのは面倒です。NetBeansに限らず、Eclipseや他の商用の開発環境のほとんどが、エラーの場合、どのような対応をすべきか候補を挙げてくれます。NetBeansの場合も図2のように行番号の15、16行目ところに電球のアイコンが表示され、ここをクリックすると対処方法の候補が表示されます。15行目の電球をクリックすると、[javax.persistence.Entityをインポートに追加]を含め4つの解決策の候補が表示されるはずです。[javax.persistence.Entityをインポートに追加]をクリックするとjavax.persistence.Entityのimport文が自動的に追加されます。

 

 これ以降の説明でJPA関連のアノテーションを説明する場合、同様な操作をするものとし操作自体の説明は割愛します。Tableアノテーションも同様に操作して確認してください。

 

図2.電球マークは解決策のヒント
図2.電球マークは解決策のヒント

 次にテーブルのカラムとフィールドの紐づけをColumnアノテーションで行います。主キーに紐づける場合はColumnアノテーションの前にIdアノテーションを付けます。ここまでのコードがリスト1です。フィールドはnameのみ表示しています。他の項目も同様にColumnアノテーションを付けます。

リスト1.Meigaraクラス抜粋
001:@Entity
002:@Table(name = "MEIGARA")
003:public class Meigara implements Serializable {
004:
005:    private int id = 0;
006:    private String name = "";
007:    private String nameKana = "";
008:    private int dosu = 0;
009:    private String koji = "";
010:    private String sweetPotatoName = "";
011:    private String manufacturer = "";
012:    private float volume = 0f;
013:    private int price = 0;
014:
015:    @Id
016:    @Column(name = "ID")
017:    public int getId() {
018:        return id;
019:    }
020:
021:    public void setId(int id) {
022:        this.id = id;
023:    }
024:
025:    @Column(name = "NAME")
026:    public String getName() {
027:        return name;
028:    }
029:
030:    public void setName(String name) {
031:        this.name = name;
032:    }

 次に、リスト2のように引数のないコンストラクタと全てのフィールドに値を設定するコンストラクタを追加します。

リスト2.コンストラクタの追加
001:    public Meigara() {
002:    }
003:
004:    public Meigara(
005:            int id,
006:            String name,
007:            String nameKana,
008:            int dosu,
009:            String koji,
010:            String sweetPotatoName,
011:            String manufacturer,
012:            float volume,
013:            int price) {
014:        this.id = id;
015:        this.name = name;
016:        this.nameKana = nameKana;
017:        this.dosu = dosu;
018:        this.koji = koji;
019:        this.sweetPotatoName = sweetPotatoName;
020:        this.manufacturer = manufacturer;
021:        this.volume = volume;
022:        this.price = price;
023:    }

 以上で、MeigaraクラスはEntityクラスの要件を満たしました。ただしクラス宣言の箇所で「プロジェクトに持続性ユニットがありません」という警告が出ています。これは無視してはいけない警告です。JPAの表舞台のプレイヤーでは説明しませんでしたが、大切な裏方さんであるため作成する必要があります。

 電球のアイコンをクリックすると[持続性ユニットを作成]と表示されるのでクリックします。[持続性ユニットを作成]画面が表示されます。[持続性ユニット名]はデフォルトで表示されている「JPASample01PU」をそのまま指定します(別の名前で構いません)。[持続性ライブラリ]のデフォルトは「EclipseLink」となっていますが、今回はTopLinkを使用しているためリストから「TopLink」を選択します。

 [データベース接続]は「実装のための準備」の説明通り行っていれば「jdbc:mysql://localhost:3306/imoshop」がリストに表示されているはずで、それを選択します。[表生成の方針]は既にmeigaraテーブルを作成しているので[なし]のラジオボタンをチェックし、[作成]ボタンをクリックします。

 プロジェクトタブの[JPASample01]-[ソースパッケージ]の下に[META-INF]が、さらにその下にpersistence.xmlが作成されているはずです。このXMLファイルの内容を確認すると図3のようにMySQLのimoshopスキーマに接続するための情報が記述されています。[持続性ユニットを作成]だけではEntityクラスの情報が不足しているため、class要素としてjp.kawakubo.Meigaraを手で追加しています。Entityクラスを作成するたびにpersistence.xmlにクラスを追加していくことになります。

図3.persistence.xmlの内容
図3.persistence.xmlの内容

CRUD操作のためのクラスを作成

Entityマネージャを生成

 プロジェクトの作成で骨格のみ作成したJPAStandaloneクラスでmeigaraテーブルのCRUD操作が行えるよう実装します。今回はJavaアプリケーションとして実装するため、Webアプリケーションサーバの恩恵を受けることはできません。表1.JPAプレイヤーズ一覧のプレイヤーを意識しつつの実装となります。

 CRUD操作を行うためにはEntityマネージャが必要ですが、Entityマネージャを生成するには、Entityマネージャファクトリを生成する必要があります。ここで先述の持続性ユニットが必要になります。リスト3がEntityマネージャを得るコードです。

リスト3.Entityマネージャを得るコード
001:        // Entityマネージャファクトリを生成する
002:        EntityManagerFactory entityManagerFactory
003:                = Persistence.createEntityManagerFactory("JPASample01PU");
004:        // Entityマネージャを生成する
005:        EntityManager entityManager = entityManagerFactory.createEntityManager();
Entityトランザクションの開始、終了

 リスト4がEntityトランザクションをEntityマネージャから取得し、トランザクションの開始、終了をするためのコードです。トランザクションの終了はコミットかロールバックで終了します。当記事ではロールバックの処理は記述しませんが、実際には、要件にしたがって適宜ロールバックを入れてください。

リスト4.Entityトランザクションの開始、終了のためのコード
001:        // Entityトランザクションを取得し、トランザクションを開始する
002:        EntityTransaction entityTransaction = entityManager.getTransaction();
003:        entityTransaction.begin();
004:        // beginとcommitの間にCRUD操作を記述する
005:        // トランザクションをコミットする
006:        entityTransaction.commit();
007:        // Entityマナーじゃをクローズする
008:        entityManager.close();
テーブルに1件挿入

 リスト4の4行目にも書いていますが、CRUD操作はトランザクションのbeginとcommitの間に記述します。それではCRUD操作を確認するためのコードを作成します。まずは既存のmeigaraテーブルに1件新しいレコードを追加します。リスト5が銘柄を1レコード追加するためのコードです。

リスト5.挿入のためのコード
001:        Meigara meigara = new Meigara(0, "千鶴", "ちづる", 25, "白", "黄金千貫", "神酒造", 1.8f, 1946);
002:        entityManager.persist(meigara);

 非常にシンプルです。

 1行目でmeigaraテーブルに対応するmeigaraというEntityオブジェクトを生成し、2行目でEntityマネージャのpersistメソッドの引数として渡します。1行目の第1引数に0を設定しているのはmeigaraテーブルのidフィールドをAUTO_INCREMENTに設定しているためです。この時点ではまだmeigaraテーブルに挿入されませんが、トランザクションのコミット処理でmeigaraテーブルに追加されます。

 ここまでのJPAStandaloneクラスのコードはリスト6のようになります。また、このクラスを実行する前のmeigaraテーブルの内容は図4の通りです。

リスト6.JPAStandalone挿入確認版
001:package jp.kawakubo;
002:
003:import javax.persistence.EntityManager;
004:import javax.persistence.EntityManagerFactory;
005:import javax.persistence.EntityTransaction;
006:import javax.persistence.Persistence;
007:
008:public class JPAStandalone {
009:
010:    public static void main(String[] args) {
011:        // Entityマネージャファクトリを生成する
012:        EntityManagerFactory entityManagerFactory
013:                = Persistence.createEntityManagerFactory("JPASample01PU");
014:        // Entityマネージャを生成する
015:        EntityManager entityManager = entityManagerFactory.createEntityManager();
016:
017:        // Entityトランザクションを取得し、トランザクションを開始する
018:        EntityTransaction entityTransaction = entityManager.getTransaction();
019:        entityTransaction.begin();
020:        // beginとcommitの間にCRUD操作を記述する
021:        Meigara meigara = new Meigara(0, "千鶴", "ちづる", 25, "白", "黄金千貫", "神酒造", 1.8f, 1946);
022:        entityManager.persist(meigara);
023:        // トランザクションをコミットする
024:        entityTransaction.commit();
025:        // Entityマネージャをクローズする
026:        entityManager.close();
027:
028:    }
029:}
図4.meigaraテーブルの内容
図4.meigaraテーブルの内容

 JPAStandaloneを実行する準備ができましたが、他のCRUD操作を確認するためのクラスも作成するため、JPAStandaloneからJPAInsertStandaloneクラスに名前を変更します。

 NetBeansの[プロジェクト]タブで、JPAStandaloneクラスを右クリックし、[リファクタリング]-[名前を変更]を選択すると[名前を変更]画面が表示されます。[新しい名前]に「JPAInsertStandalone」と入力し[リファクタリング]ボタンをクリックすると名称が変更されます。

meigaraテーブルへ1件追加

 実行はいたって簡単です。JPAInsertStandaloneを右クリックし[ファイルを実行]を選択すると実行されます。実行した結果のmeigaraテーブルの内容は図5の通りで、idが9であるレコードが1件追加されているのが確認できます。

図5.挿入後のmeigaraテーブルの内容
図5.挿入後のmeigaraテーブルの内容

 図5はMySQL Workbenchの画面キャプチャですが、CRUD操作ごとに確認するのは大変なので、検索用のメソッドを作成し表示するのが簡単ですが、JPQLを説明しないと分かりづらいため、当記事では実装しません。JPQLについては後編で説明します。今回はMySQL Workbenchの画面キャプチャで内容を確認します。

更新、削除Javaアプリケーションの作成

 いったん環境さえ整えば、更新や削除のコードを書くのは簡単です。

更新アプリケーションの作成

 JPAInsertStandaloneクラスで挿入したレコードを更新するためのJavaアプリケーションを作成します。作成はいつも通り、[プロジェクト]-[JPASample01]で右クリックして[新規]-[Javaクラス]を選択し、画面が表示されたら[クラス名]に「JPAUpdateStandalone」、[パッケージ]に「jp.kawakubo」と入力します。

 リスト7が更新用のJavaアプリケーションです。本来であれば容量(volume)が900mlで価格(price)が1050円であったが、間違えて入力したためそれを更新する、という仮定でコーディングしています。

リスト7.JPAUpdateStandaloneクラス
001:package jp.kawakubo;
002:
003:import javax.persistence.EntityManager;
004:import javax.persistence.EntityManagerFactory;
005:import javax.persistence.EntityTransaction;
006:import javax.persistence.Persistence;
007:
008:public class JPAUpdateStandalone {
009:    public static void main(String[] args) {
010:        // Entityマネージャファクトリを生成する
011:        EntityManagerFactory entityManagerFactory
012:                = Persistence.createEntityManagerFactory("JPASample01PU");
013:        // Entityマネージャを生成する
014:        EntityManager entityManager = entityManagerFactory.createEntityManager();
015:
016:        // Entityトランザクションを取得し、トランザクションを開始する
017:        EntityTransaction entityTransaction = entityManager.getTransaction();
018:        entityTransaction.begin();
019:        // beginとcommitの間にCRUD操作を記述する
020:        // findメソッドでmeigaraテーブルよりMeigaraオブジェクトを取得する
021:        // 第2引数はIdアノテーションで指定したカラムの値
022:        Meigara meigara = entityManager.find(Meigara.class, 9);
023:        meigara.setVolume(0.9f);
024:        meigara.setPrice(1050);
025:        // トランザクションをコミットする
026:        entityTransaction.commit();
027:        // Entityマネージャをクローズする
028:        entityManager.close();
029:
030:    }
031:}

 リスト7の通り、ほとんどJPAInsertStandaloneと同じです。違うのは22から24行目のみです。

 22行目で主キーが9であるレコードからMeigaraオブジェクトを取得します。値の変更は通常のPOJOオブジェクト同様setterメソッドで値を設定するだけです。SQL文がコードに混じりこまず非常に分かりやすいコーディングになっています。

 実行するにはJPAUpdateStandaloneを右クリックし[ファイルを実行]を選択すると実行されます。実行後のmeigaraテーブルの内容は図6の通りです。volumeが0.90に、priceが1050に更新されているのが確認できます。

図6.更新後のmeigaraテーブルの内容
図6.更新後のmeigaraテーブルの内容

削除アプリケーションの作成

 削除アプリケーションの作成も更新アプリケーションとロジックはほとんど変わりません。リスト8はJPADeleteStandaloneという名前で作成した削除アプリケーションのうち、トランザクションのbeginからcommitの間のみ抜粋したものです。削除後のmeigaraテーブルの内容は図7の通りでidが9であるレコードがなくなっているのが確認できます。

リスト8.JPADeleteStandaloneクラス(抜粋)
001:        // findメソッドでmeigaraテーブルよりMeigaraオブジェクトを取得する
002:        Meigara meigara = entityManager.find(Meigara.class, 9);
003:        entityManager.remove(meigara);

 2行目で削除したいレコードのEntityクラスを第1引数に、その主キーを第2引数に設定しMeigaraオブジェクトを取得しています。削除するには取得したオブジェクトをEntityマネージャのremoveメソッドの引数に設定するだけです。

図7.削除後のmeigaraテーブルの内容
図7.削除後のmeigaraテーブルの内容

おさらい

 JPAはO/Rマッパーの違いを隠ぺいするためのAPIです。

 JPAを使ってO/Rマッピングを行うにはJPA関連のjarファイルとJDBCドライバのjarファイルが必要です。今回はマッパーとしてGlassFish内蔵のTopLink Essentialを、データベースとしてMySQLを使用しました。また、それらのjarファイルをライブラリに追加しました。

 CRUD操作を行うためにはEntityマネージャのメソッドを使用します。

  • 挿入の場合、Entityマネージャのpersistメソッドを使用する
  • JPQLを使用する(後編で説明)
  • 更新の場合、更新したいレコードに相当するEntityクラスとその主キーを、Entityマネージャのfindメソッドの引数に設定しEntityオブジェクトを取得する。取得したEntityオブジェクトの更新したいフィールドのsetterメソッドに更新したい値を設定する
  • 削除の場合、削除したいレコードに相当するEntityクラスとその主キーを、Entityマネージャのfindメソッドの引数に設定しEntityオブジェクトを取得する。取得したEntityオブジェクトをEntityマネージャのremoveメソッド引数に設定する

 今回は、1テーブルに対するCRUD操作を行うための実装方法を説明しました。実際のシステムではテーブルは複雑に関連しています。複数のテーブルのCRUD操作については後編で説明します。

サンプルコード

 当記事のプロジェクトのフォルダ構成は図8の通りです。persistence.xmlとソースコード、およびデータベースリストア用のsqlファイルを「サンプルコード」としてページトップに置いています。ダウンロードし解凍してご利用ください。

図8.プロジェクトのフォルダ構成
図8.プロジェクトのフォルダ構成