Java Persistence での主キーの使用

Sean BrydonSmitha Kangath
ステータス:Early Access

課題

持続性エンティティーには主キーが必要です。このドキュメントでは、Java Persistence API を使用したアプリケーションのモデル層の開発で、主キーを使用する際の指針とヒントを扱っています。最初に主キーの定義方法を説明し、続いて生成方法を取り上げます。

対処法

主キーに関して従うべき指針がいくつかあります。

単純か複合かの選択

主キーの型は、Java プリミティブ型 (intbytelong など) かプリミティブラッパー型 (IntegerByteLong など)、java.lang.String 型、java.util.Date 型、java.sql.Date 型のいずれかにできます。アプリケーション開発者は、型の選択に加えて、単純主キーまたは複合主キーのどちらを使用するかを選択する必要があります。

単純主キーには、エンティティーの持続性フィールドまたはプロパティーが 1 つ必要です。単純主キーであることを示すには、@Id 注釈を使用します。エンティティー中の @Id 注釈で定義されたフィールド (またはプロパティー) は、対応するデータベース表の主キーにマッピングされます。コード例 1 は、単純主キーの定義を示しています。

@Entity public class Item {
  ...
  //デフォルトの列マッピングを使用
  String itemId;

  @Id
  public String getItemId() {
    return itemId;
  }
 ...
}
コード例 1: 単純主キー

複合キーは、1 つの持続性フィールドか、または一連のフィールドのグループのいずれかに対応し、主キークラスによって表されます。通常、複合キーは表のいくつかの列を含む埋め込みクラスとして定義されます。複合キーは、対応するデータベース表の主キーが複数の列から構成される場合に有用です。 複合キーは、@EmbeddedId または @IdClass 注釈で定義できます。@EmbeddedId 注釈を使用する場合、複合キーを表すクラスは @Embeddable 注釈で指定された埋め込みクラスです。 複合キークラスはシリアライズ可能で、equals メソッドおよび hashCode メソッドを定義する必要があります。次のコード例 2 は、@EmbeddedId 注釈を使った複合キーの定義を示しています。

@Embeddable public class ItemId implements Serializable {
    private String firstName;
    private String lastName;
    ...
   
}

@Entity public class Item implements Serializable {
    private ItemId id;
    @EmbeddedId public ItemId getId() {
        return id;
    }
    ... 
}

コード例 2: @EmbeddedId 注釈を使用した複合主キー

主キーの生成方法

主キーは、データベース、コンテナ、またはアプリケーションそのものによって生成されます。主キーがプロバイダまたはデータベースによって生成された場合、その主キーは自動生成されたと言います。

アプリケーションにおける主キーの生成

このオプションは、データに内在する主キーを使用する方法です。この場合、データにはすでに主キーがあるため、主キーを生成したり、割り当てたりする必要はなく、アプリケーションは単にデータの主キーを利用できます。たとえば、米国の納税者を表すエンティティーの社会保障番号がこれに当たります。アプリケーションデータに主キーが含まれていない場合は、データを作成してデータベースに格納するときに、アプリケーションはデータに主キーを割り当てる必要があります。アプリケーションは、プログラム的に主キーの値を作成して設定します。これを行う 1 つの方法は、シーケンス番号などの ID からなる表と、その表にマッピングするオブジェクトを作成し、IdGenerator.getNextId() などの API を使用する方法です。単に System.currentTimeMillis() を呼び出す方法もあります。ただし、これらのメソッドはクラスタ化された環境で期待通りの働きをしないことがあります。

アプリケーションに主キーを生成させるメリットは、データベースでデータを持続させる前にキーがわかることです。

主キーの自動生成

もう 1 つのオプションは、ID を自動的に生成して、プロバイダまたはデータベースにその値の設定を任せる方法です。その場合は、アプリケーション開発者が ID 生成ロジックを作る必要はありません。単に ID で注釈を使用して、プロバイダまたはデータベースに処理を任せます。

Java Persistence には、@GeneratedValue 注釈を使用して、この方法で主キーを自動生成する仕組みが用意されています。任意の ID の生成方法と、任意の ID ジェネレータを提供します。@GeneratedValue 注釈は @Id 注釈と組み合わせます。

持続性プロバイダは、主キーを生成するのにいくつかの生成方法を使用します。TABLE、SEQUENCE、IDENTITY および AUTO の生成方法です。

IDENTITY 生成では、データベースがアイデンティティー列を追加して、主キーの生成を担当します。 これはデータベースに固有の方法で、データベースが IDENTITY 列型をサポートしている必要があります。このため、この方法は、データベースにまたがって使用できないことがあります。

SEQUENCE 生成では、持続性プロバイダは主キーを生成するデータベースシーケンスを使用します。そのためにはデータベースがシーケンスをサポートしている必要があるため、データベースにまたがって使用できないことがあります。

TABLE 生成では、基のデータベース表に基づいて値が割り当てられます。この方法はデータベースに固有の機能に基づいていないため、データベースにまたがって使用できます。

AUTO 生成は、持続性プロバイダに適切な生成方法を選択させます。

SEQUENCE、IDENTITY、および AUTO 生成は、データベースにまたがって使用することはできません。また、AUTO、SEQUENCE、および TABLE 生成はクラスタ化された環境でうまく機能しないことがあります。これら 3 つの方法はどれも、プロバイダが何らかの処理を行う必要があります。プロバイダにこの処理能力がない場合、データの整合性が失われることがあります。IDENTITY 生成では、プロバイダなしでデータベースが処理を行うため、クラスタ化された環境でも、データの整合性が失われません。

GeneratedValue 注釈の 1 要素として指定する ID ジェネレータは、SequenceGenerator 注釈または TableGenerator 注釈を使用して宣言する必要があります。どちらの注釈を使用するかは、生成方法が SEQUENCE または TABLE のどちらであるかに依存します。これらの注釈の宣言は両方とも、エンティティークラスまたは主キーフィールドのどちらでも行うことができます。ジェネレータのスコープは持続性ユニットです。

TABLE 生成の場合に主キーがどのように自動生成されるかを見てみましょう。表ジェネレータの場合、持続性プロバイダはデータベース表を使用して生成された ID を保持します。1 つのエンティティーが表の 1 行になります。表には主キー名列と値列がそれぞれ 1 つできます。 ジェネレータの表を作成する SQL コマンドは次のようになります。

CREATE TABLE ID_GEN(GEN_KEY VARCHAR(10) NOT NULL, GEN_VALUE INTEGER NOT NULL,primary key (GEN_KEY));

この表には、ジェネレータ用のシードとして 1 行を挿入する必要があります。
INSERT INTO ID_GEN VALUES('ITEM_ID',101);

これで、項目 ID を主キーとする Item 表がある場合、生成される ID は 102 からスタートします。 Item の持続性エンティティーでは、TableGenerator 注釈でこの表を参照する必要があります。この注釈は次のようになります。

  @TableGenerator(name="ID_GEN",
            table="ID_GEN",
            pkColumnName="GEN_KEY",
            valueColumnName="GEN_VALUE",
            pkColumnValue="ITEM_ID",
            allocationSize=1)
  @GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GEN")
  @Id
  public int getItemID() {
      return itemID;
  }
  ...


一般的なユースケースとして、既存のデータベースがあり、新しく挿入されるレコードに主キーを自動生成するケースがあります。これは、既存の主キーがシーケンシャル型の場合に可能です。

主キーの保守

あらゆるエンティティーは主キーを持つ必要があります。この主キーはエンティティーがデータベースに初めて保持される場合に設定されます。 アプリケーションが Java エンティティーの主キーの値を変更してはいけません。 アプリケーションが主キーの値の変更しようとするときの実際の動作は、定義されていません。アプリケーションが主キーを本当に変更する場合、参照整合性が失われないようにしながら、まずエンティティーを削除して、それから新しい主キーを使ってエンティティーを追加します。ほとんどの場合、アプリケーションが主キーを変更する必要はありません。たとえば、自動生成した ID を使用する場合に、アプリケーションがその値を設定する必要はありません。エンティティーには、アプリケーションコードが変更してはいけないアイデンティティーフィールド (または主キークラス) があるため、コード内に主キーの変更を防止する防御策があると便利です。1 つの方法として、setPK(.. ) メソッドまたはクラスで、主キーの設定メソッドを呼び出したり、主キーフィールドに直接アクセスして変更したりしないように、Javadoc を通じてユーザーにドキュメントで指示することができます。しかしながら、この方法だけでは十分ではありません。なぜなら、エンティティーの開発者は、チームのほかの開発者がエンティティーを適切に使用しやすいようにしたいからです。たとえば、エンティティーを呼び出すコードを作成する Web 開発者が、エンティティーの主キーの値を誤って変更してしまうことがあります。

エンティティーのフィールドやメソッドにアクセスするアプリケーションのほかに、コンテナや持続性プロバイダもまた、エンティティーにアクセスすることがあります。コンテナは、アプリケーションコードの指定によっては、フィールドまたはプロパティーメソッドのいずれかを使ってエンティティーにアクセスします。コンテナ/プロバイダは、エンティティー内のデータとデータベース内のデータ間の対話を管理し、それらの同期を維持するなどの仕事をするため、主キーの値にアクセスする必要があります。コンテナはまた、ID が自動生成される場合に主キーの値を割り当てるためにエンティティーにアクセスします。このため、主キーが誤って変更されるのを防止するためにあらゆる方法を使用しても、コンテナがエンティティーの主キーフィールド (または主キークラス) にアクセスする必要があることを考慮する必要があります。

エンティティーのユーザーが主キーを誤って変更しないような方法で、エンティティー Bean を開発する方法を考えてみましょう。この方法には次の 5 つの手順があります。

参考資料


© Sun Microsystems 2006. Java BluePrints Solutions Catalog の内容はすべて著作権保護されており、サン・マイクロシステムズ社の書面による許可なしに他の著作物に発表することを禁止します。