JPA をめぐる話題

メニュー

初めに

 ここでは順不同に、JPA に関する話題を書いていきます。事の成り行き上、まとまりに欠けることはご了解ください。また、新しい項目に気が付いた場合には、随時内容を増やしまた修正していきたいと思います。

エラーの発生原因を探る方法

 まずここで書くことは、Web サーバ特に、JPA の実装に依存しています。お使いの Web サーバでは違ったアプローチが必要になる点に注意してください。

  JPA を使用している場合、エラー発生時にそのエラーの原因にたどり着くことが容易ではありません。このサンプルでのエラー処理は、 ここ にあるように、管理 Bean の errorOccurred メソッドをオーバーライドすることになります。この引数に対して getMessage メソッドを呼び出した値があまりにもおおざっぱで、エラー原因の特定に役立たないのです。ではどうするか?

 まず2段階で進みます。エラーの原因にたどり着くには、開発環境を使用して errorCccurred メソッドの内部にブレイクポイントを設定します。その位置で停止したあと、引数の名前を選択して、マウスの右ボタンメニューから「新規ウォッチに追加」を選択します。こうすることで例外の内容を確認できます。続いて、表示内容を展開し、「継承」となっている項目を展開し、その「cause」となっている変数を展開し、さらに「継承」を展開します。おそらくそこに表示されているエラー内容が本当の原因に対応していると思われます。
 この操作は、デバッガを使用して例外の発生順番を根元に根元にとたどったことになります。この操作でまだ原因にたどり着けない場合は、「cause」から「継承」とより深く展開する作業を続けてください。

 次に、プログラムからエラーの原因にたどり着き、例えば何らかのメッセージを表示したい場合を考えます。上記で「cause」を展開することは、例外のインスタンスに対して getCsuse メソッドを必要な回数呼び出すことに該当します。何度呼び出したらよいかは、デバッガを使用して確認しておきます。そして該当するインスタンスにたどり着いて、必要な変数をリフレクションで読みだすことになります。

 とても面倒だと思います。この問題は、データベースのプライマリーキーの2重違反を知りたいと思ったときに遭遇しました。ネイティブのデータベースエラーメッセージやエラーコードに簡単にアクセスできる方法が提供されていないことが問題だと思います。

Identity 列を指定する方法

 データベースのプライマリーフィールドに identiry を指定して、データベースサーバに自動附番させることがあります。この場合のエンティティクラスの例を示します。このサンプルでは Knowledge クラスの id フィールドがそれに該当します。

                     
public class Knowledge implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    private Integer id;
    
    …
}                         

                 

  id 変数に対して、3つのアノテーションが付いています。 @Id はキー項目であること、@GeneratedValue(strategy=GenerationType.IDENTITY) は自動附番する項目であること、@Column(name = "ID", nullable = false) はテーブルのフィールド名が ID であり、 null 禁止であることを示します。
 この指定により、データの追加時( persist 時)には id 変数が空白で正しく動作します。

  id 変数はデータベースへの追加が成功した場合には正しい値で初期化されていますので、必要に応じて自動附番された値を参照することができます。

Entity クラスがデータベースと同期されない場合の対処方法

 プログラムを見直しても原因が不明で、エンティティの値がデータベースと同期されない場合があった場合、以下のことを試してみる価値があります。エンティティクラスのインスタンスは、 EntityManager キャッシュされている場合があります。そのキャッシュをやめるには以下のようにします。

                     
@Cacheable(false)
public class MyEntityClass implements Serializable {
    …
}                         

                 

実際の SQL を確認する方法

  JPA を使用していると実際に発行される SQL を目にすることはありません。しかしこれではプログラム開発時には困りますし、思ったような速度が出ない場合には特にそうです。この場合には、persistence.xml に以下2行の記述を追加します。 persistence.xml ファイルは該当プロジェクトの「構成ファイル」フォルダー以下にあります。

 ただし、この方法は JPA の実装依存になります。

                     
    <properties>
1       <property name="eclipselink.logging.level.sql" value="FINE" />
2       <property name="eclipselink.logging.parameters" value="true" />
    </properties>       

                 

 1行目で、詳細なログを指定し、2行目でパラメータの出力を指定しています。またこの表示は、GlassFish のコンソールに出力されます。 NetBeans の右下にある「出力」ペインの「GlassFish Server」タグを選択してください。

Entity クラスの自動生成の問題

 Entity クラスを NetBeans から自動生成する場合の問題に関しては、 ここ を参照してください。

複合プライマリーキーの問題

 外部参照キーがある場合の問題は、 ここ を参照してください。

楽観的制御の問題

 楽観的排他制御の問題は、 ここ を参照してください。

データ削除の問題

  EntityManager からデータベースへの DELETE を発行するためには、その対象となるオブジェクトは EntityManager の管理対象となっている必要があります。しかし新規に new で作成したオブジェクトは、 EntityManager の管理対象になっていません。その場合は、 EntityManager に対して merge(anObject) を呼び出して管理対象にする必要があります。

 ここで問題は、その merge() の引数としたオブジェクトが管理対象となるのではなく、管理対象になった新規のオブジェクトが merge() の戻り値として返ることです。そのため、 merge() の戻り値を保存しておき、そのオブジェクトに対して remove() を呼び出す必要があります。

ストアドプロシージャの利用方法

 データベースを利用する上でよく利用されるのがストアドプロシージャです。ここでは Java からストアドプロシージャを利用する方法を書きます。この方法が利用できるのは JPA2.1 から、 Java EE のバージョンでは Java EE 7 からになります。

 データベースで定義されたストアドプロシージャのサンプルはこれです。

                     
USE [KnowledgeDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[ProcMinorTable]
	@MajorID	nchar(2),
	@MinorID	nchar(2) 
AS 
	DECLARE @strSQL nvarchar(4000)

	SET @strSQL=''
	SET @strSQL=@strSQL+ 'SELECT I.MID, I.ID, I.Name, I.Comment, A.Name As MajorName '
	SET @strSQL=@strSQL+ 'FROM Minor As I INNER JOIN Major As A ON A.ID = I.MID WHERE 1 = 1 '
	IF @MajorID is not null AND @MajorID <> '' BEGIN
		SET @strSQL=@strSQL+' AND I.MID=''' + @MajorID +''''
	END
	IF @MinorID is not null AND @MinorID <> '' BEGIN
		SET @strSQL=@strSQL+' AND I.ID=''' + @MinorID +''''
	END
	SET @strSQL=@strSQL+' ORDER BY I.MID, I.ID '

	PRINT(@strSQL)
	EXECUTE(@strSQL)                         

                 

 このサンプルで使用しているデータベースの情報は、 こちら を参照してください。

 簡単に説明します。基本は小分類テーブルを検索ための select 文を発行しています。ただし、大分類コードから該当する大分類名をジョインしています。引数として、大分類コードと小分類コードの2つを使用しています。

 次はこのストアドプロシージャを利用している Java プログラムです。MinorDao クラスのメソッドとして定義しました。MinorDao クラスや Findable インターフェースなどに関しては、 こちら を参照してください。

                     
     public Findable findByKeys(final String majorId, final String minorId) {
1       return new Findable() {
2           @Override
3           public List select(EntityManager em) throws Exception {
4               StoredProcedureQuery query = em.createStoredProcedureQuery("ProcMinorTable");
5               query.registerStoredProcedureParameter(1, String.class, ParameterMode.IN);
6               query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);

7               query.setParameter(1, majorId);
8               query.setParameter(2, minorId);

9               return query.getResultList();
            }
        };
    }                       

                 

 2つの引数、大分類コードと小分類コードをもつメソッド findKeys() メソッドとして定義しています。1行目は、Findable インターフェースを実装したオブジェクトを返します。3行目以降は Findable インターフェースの実装になります。4行目で ProcMinorTable のストアドプロシージャ名を指定しています。5行目、6行目はストアドプロシージャの2つの引数定義しています。7行目、8行目で実際の引数を受け渡ししています。9行目でストアドプロシージャを呼び出して、検索結果を返しています。

 このメソッドの戻り値の扱いに注意が必要です。検索結果は複数の可能性がありますので、リストで返されます。問題はその個々のリストの要素です。ストアドプロシージャの戻り値をもう一度確認しましょう。大分類コード、小分類コード、小分類の名前、小分類のコメント、そして大分類の名前の5つが返されます。つまり、Object[5] の構造を持つデータが返されます。このストアドプロシージャの場合はたまたますべて文字列ですので、5つの要素はいずれも String のインスタンスとなります。例えば数値型のデータが返る場合は、 int のような基本型ではなく Integer のようなクラス型が返されます。ストアドプロシージャの戻り値を利用するのは少々面倒ですが、このやり方で Java からストアドプロシージャを呼び出すことができます。