JBoss EJB 3.0と拡張機能

  エンティティBeanのO/Rマッピング
はじめに

データベース駆動のアプリケーションを作るためには、Java開発者は通常2つの異なる構造でデータを管理・モデル化します。

  • アプリケーション内部では、そのデータはヒープメモリ上でJavaオブジェクトによってモデル化されます。Javaアプリケーション全体は、これらのデータオブジェクトの集まりによって形成されます。Java言語とAPIはオブジェクトを効率的に扱えるよう設計されています。そしてJava開発者はオブジェクトモデルによって生産性を向上させることができます。
  • そのデータがリレーショナルデータベース上で格納されるとき、それはデータベースのテーブル上でモデル化されます。リレーショナルデータベースは高速で信頼性の高い永続ストレージ技術です。それは分散環境で動作しますし、大量データセットでも効率的な検索が可能です。

Javaオブジェクトとリレーショナルテーブルを操作するには異なるシンタクスやAPIが必要になります。それゆえ、アプリケーションの全体を通して、開発者は同じデータに対して2回のモデル化をして、これら2つのシステム間でデータを切り替える必要があるのです。これは効率が悪く、間違いやすいやり方です。

エンティティBeanはアプリケーションデータのオブジェクト表現とリレーショナル表現間のこのギャップを埋めるために設計されました。Java開発者の視点から見ると、EJB 3.0エンティティBeanとは、オブジェクトがどのようにデータベースに格納されるのかを示したアノテーションの付いた単なるPOJO(plain old Java object)に過ぎません。オブジェクトからリレーショナルデータベースのテーブルへのマッピングは、EJB 3.0によって自動的かつ透明に行われます。Java開発者はもはやデータベースのテーブルスキーマ、データベースのコネクション管理、特定のデータベースアクセスAPIの詳細について知る必要はないのです。

サンプルアプリケーション内のオブジェクト構造

これからのトレイルでは、EJB 3.0エンティティBeanの使い方をデモするために新しい投資計算アプリケーションを開発します。この新しい投資計算プログラムには投資会社(fund company)のプロファイルのリストや個人投資家(indivisual investor)のプロファイルのリストをリレーショナルデータベースに格納できます。それぞれの投資計算では、データベースから投資会社と投資家を選択し、次に月掛貯金(monthly saving amount)を入力することで、最終的な投資収益(investment return)を計算できます。各計算記録も、タイムスタンプ付きでデータベースに保存されます。データベース上では後から計算記録の検索と更新が可能です。

上記の投資計算アプリケーションにおけるデータをモデル化するため次のようなオブジェクトクラス階層を構築します。すべてのクラスは単純なJavaBeansコンベンション(つまり、データ属性にアクセスするため標準的なgetter、setterメソッド)に従います。

  • Fundクラスは利用可能な投資ファンドのプロファイルを表現します。それは名前属性と平均年成長率属性を持ちます。
  • Investorクラスは個人投資家のプロファイルを表現します。それはデータ属性として投資家の名前、投資の開始年齢と終了年齢を含みます。
  • Recordクラスは投資計算を表現します。それはデータ属性としてFundオブジェクト、Investorオブジェクト、月掛貯金(monthly saving amount)、投資収益を含みます。
  • TimedRecordクラスは時刻情報付き計算記録を表現します。それはRecordクラスを継承し、タイムスタンプのデータ属性が追加されています。

これらクラス間の関係は次図で表現されます。

FundエンティティBean

@Entityアノテーションのタグをつけると、Fund POJOクラスはエンティティBeanになります。通常は、EJB 3.0コンテナは各エンティティBeanクラスをデータベース内の単一テーブルにマップします。@Tableアノテーションはコンテナに対してテーブル名を教えます。エンティティBeanの各インスタンスは、そのテーブルにおけるデータの行を表現します。そのテーブルの各カラムはエンティティBeanのデータ属性に相当します。以下はFundエンティティBeanクラスから抜粋したものです。


@Entity
@Table(name = "fund")
public class Fund implements Serializable {
  private int id;
  private String name;
  private double growthrate;

  public Fund () { }

  public Fund (String name, double growthrate) {
    this.name = name;
    this.growthrate = growthrate;
  }

  @Id
  @GeneratedValue
  public int getId () {
    return id;
  }

  public void setId (int id) {
    this.id = id;
  }

  public String getName () {
    return name;
  }

  public void setName (String name) {
    this.name = name;
  }

  // Other getter and setter methods ...
}

@Idアノテーションは指定したId属性がそのテーブルの主キーであることを示します。@GeneratedValueアノテーションはその主キーの値がサーバによって自動生成されることを表します。EJB 3.0コンテナは、データベースに格納できるように各Fund Beanインスタンスに対してそのidを設定します。このクラスでは、EJB 3.0コンテナはデフォルトとなるカラム名としてJava Bean属性名を使用します。マップされるデータベーステーブルのスキーマは次のようになります。

The Investor entity bean

Fund Beanと同様に、Investorクラスは、データベーステーブルに直接マップ可能なもう一つの簡単なエンティティBeanです。以下はInvestorクラスからの抜粋です。


@Entity
@Table(name = "investor")
public class Investor implements Serializable {
  private int id;
  private String name;
  private int startAge;
  private int endAge;

  public Investor () { }

  public Investor (String name, int startAge, int endAge) {
    this.name = name;
    this.start = startAge;
    this.end = endAge;
  }

  @Id
  @GeneratedValue
  public int getId () {
    return id;
  }

  public void setId (int id) {
    this.id = id;
  }

  public int getStartAge () {
    return startAge;
  }

  public void setStartAge (int startAge) {
    this.startAge = startAge;
  }

  // Other getter and setter methods ...
}

次図はマップされた投資家(investor)テーブルのデータベーススキーマを示します。

RecordエンティティBean

RecordエンティティBeanのマッピングはFundInvestor Beanのマッピングよりもさらに複雑です。RecordFundInvestor Beanのような、それ自身がエンティティBeanであるような属性を含みます。これらの関係をマップするには、Record Beanのテーブルは外部IDカラムを持ちます。各Record行の外部IDカラムは、FundInvestorテーブル内の関連するデータ行のそれぞれの主キーを含みます。関係する属性に@JoinColumnタグのアノテーションを付加することによって外部IDカラム名を指定することが可能です。もし、このアノテーションを省略すれば、EJB 3.0サーバはユーザのためにデフォルトのカラム名を選択してくれます。

アノテーションは外部キーの制約を指定するためにも使用することができます。例えば、@ManyToOneは各Recordが一つのFund Beanと一つのInvestor Beanだけに関連付けられることを指定します。一方、各FundInvestor Beanは、複数のRecord Beanと関連付けることが可能です。EJB 3.0コンテナは自動的にこれらの制約をデータベース上に強制します。EJB 3.0におけるエンティティの関係アノテーションは次のものを含みます。

  • @ManyToOne: それぞれのBeanインスタンス(例えばRecord)で、このアノテーションの付いた属性は一つの外部エンティティBeanオブジェクト(例えばFund)に関連付けられます。しかし、複数Beanインスタンスは同一の外部オブジェクトに関連づけることもできます。
  • @OneToMany: それぞれのBeanインスタンス(例えば「本」)で、このアノテーションの付いた属性は多くの外部エンティティBeanオブジェクト(例えば「本の著者」)に関連付けられたCollectionになります。
  • @OneToOne: それぞれのBeanインスタンス(例えば「人」)で、このアノテーションの付いた属性は一つの外部エンティティBeanオブジェクト(例えば「社会保障番号」)に関連付けられます。二つのBeanインスタンスを同じ外部オブジェクトに関連付けることはできません。
  • @ManyToMany: それぞれのBeanインスタンス(例えば「従業員」)で、このアノテーションの付いた属性は複数の外部エンティティオブジェクト(例えば「肩書き」の配列)に関連付けられます。複数のBeanインスタンスは同一の外部オブジェクトに関連付けることができます。

以下はRecordクラス関連の抜粋です。


@Entity
@Table(name = "record")
// ... ...
public class Record implements Serializable {
  protected int id;
  protected Fund fund;
  protected Investor investor;
  protected double saving;
  protected double result;

  // ... ...

  @ManyToOne(optional=false)
  @JoinColumn(name="my_fundid")
  public Fund getFund () {
    return fund;
  }

  @ManyToOne(optional=false)
  // Use the system-specified join column
  public Investor getInvestor () {
    return investor;
  }

  // Other getter and setter methods ...
}

次図はRecordエンティティBeanをマップするテーブルのスキーマを示します。my_fundidinvestor_idカラムは投資(fund)と投資家(investor)属性のための外部キーカラムです。

Recordテーブルの残りのカラムについては次のセクションで議論します。

TimedRecordエンティティBean

TimedRecordクラスはRecordクラスを継承します。インヘリタンス関係をマップするには異なるいくつかの方法が存在します。例えば、階層上の各クラスに対して別々のテーブルを使い、その関係に制約を設定するために外部キーを使うことが可能です。このトレイルでは、単一テーブルマッピング戦略(sigle table mapping strategy)というマッピング戦略を説明します。

単一テーブルマッピング戦略はEJB 3.0でのエンティティBeanのデフォルトとなるマッピング戦略です。それは基底となるRecordクラスとTimedRecordクラスの両方をを単一テーブルへマップします。このテーブルはそのクラス階層上のすべてのクラスのすべての属性を保持するためのカラムを持ちます。このテーブルの特殊なカラムが、特定の行がどのサブクラスのものかを識別するために使われます。マッピング戦略と識別カラム(differentiator column)は継承階層全体の基底クラスで指定されなければなりません。Recordクラスに@DiscriminatorColumn@DiscriminatorValueアノテーションのタグを指定しています。この例では、Recordテーブルのrecord_typeカラムがクラス識別子(class differentiator)です。もしそれが値Bを持てば、現在行は(基底クラスである)Recordクラスのインスタンスを表現します。


@Entity
@Table(name = "record")
@DiscriminatorColumn(name="record_type")
@DiscriminatorValue(value="B")
public class Record {
    // ... ...
}

TimedRecordクラスはRecordクラスを単に継承します。その@DiscriminatorValueアノテーションは、TimedRecordオブジェクトを表現するすべての行が値Tを識別カラムrecord_typeを持つことを指定します(このカラムは基底クラスRecord@DiscriminatorColumnアノテーションによって指定されます)。


@Entity
@DiscriminatorValue (value="T")
public class TimedRecord extends Record {

  private Timestamp ts;

  // ... ...

  public Timestamp getTs () {
    return ts;
  }

  public void setTs (Timestamp ts) {
    this.ts = ts;
  }

}
ソースコード参照

エンティティBean

まとめ

O/RマッピングはエンティティBeanの背後にあるキーとなる概念です。EJB 3.0はJava POJOをデータベーステーブルへ自動的かつ透明にマッピングします。次からのトレイルでは、アプリケーションでのエンティティBeanオブジェクトの使用方法を学びます。