このガイドについて

このTorqueユーザーズガイドは、Torqueをプロジェクトに導入して、アプリケーションオブジェクトに永続性の手段を提供しようとしている人たちを助けるためのものです。

継承とオブジェクトリレーショナルマップ

Torqueはオブジェクト指向における継承を扱うことが可能です。 オブジェクト-リレーショナルマッピングの設計には、一般的に3つの方法があるとみなされています。 Torqueではその中からもっとも速い手法、クラス階層中のすべてのオブジェクトをひとつのテーブルにマッピングする手法を使用します。 階層中の各クラスの全ての属性はテーブルへ格納されます。 MonitorとKeyboardというサブクラスを持つ抽象クラス、ComputerComponentについて考えてみましょう。 これらは一つのテーブルとなります - MonitorとKeybordオブジェクトは両方とも、 同じテーブルへ永続化されます。 テーブルはComputerComponentの全ての属性、Monitorが持つユニークな属性、 およびKeyboardが持つユニークな属性で構成されます。 テーブルのKeyboardオブジェクトの行は、Monitorが持つデータのカラムに対してはNULLを持ち、その逆も同様です。

他に速い手法として、個々の具象クラスを個別のテーブルにマッピングするものがあります。 各オブジェクトは、そのクラスのテーブルのひとつの行に全ての属性を格納します。 例として、Roomクラスを継承したKitchenクラスがある場合、記憶領域として2つのテーブルを必要とします。 Kitchen用テーブルはRoom用テーブルの全てのカラム、さらにKitchenに追加記述された属性のカラムを含みます。

もっとも遅く、しかしもっともオブジェクト指向的な手法として、 個々のクラスをそれぞれのテーブルに格納するものがあります。 この手法では派生したクラスに加えられた属性だけが、そのテーブルに格納されます。 永続化レイヤは記憶領域からオブジェクトを読み出すために、 テーブルを連結する必要があるでしょう。 オブジェクトは多数のテーブルに渡り分割されるので、 オブジェクトの保存はより複雑となります。 KitchenとRoomの例では、この手法でも2つのテーブル、KitchenおよびRoomがありますが、 KitchenテーブルはRoomクラスには無い属性のみを含みます。

第1の手法(Torqueが使用しているもの)の利点の一つは、 上に記述された第3の手法のように、連結を必要としないということです。 別の利点は、第2の方法よりデータモデルを維持することがより容易であるということです。 関連するクラスが交差しない属性の集まりを持つ場合には、 テーブル内の行がいくつかのnullとなるカラムを持つので、 クラス階層のモデル表現は思い通りにいかなくなります。

詳細は、リレーショナルデータベースへオブジェクトをマッピングすることについて検討しているScott Amblerのすばらしいウェブサイト、 AmbySoft.comを訪れてください。

クラス階層

       A
       |
     -----
    |     |
    B     C
    |
    D

テーブルAの特定の行がどのクラスを表しているかを示すために、torqueによって生成されるPeerには、2つの方法が組み込まれています。 行は、クラスを識別することができるきるように、いくつかの情報を持つ必要があるでしょう。 この目的にあったカラムを、属性"inheritance"と共に指定すべきです。

<table name="A"...>
  ...
  <column name="FOO" inheritance="single" type="VARCHAR".../>
</table>

この場合、カラムFOOではフルクラスネームを指定する必要があります。FOOの有効な値は以下のようになります:

com.mycompany.project.om.A
com.mycompany.project.om.B
com.mycompany.project.om.C
com.mycompany.project.om.D

これは記憶領域においてわずかに非効率的であり、 またPeerのコードも非効率となります。 なぜならば、各レコードを取得しClass.forName(FOOの値)を呼び出すまで、 どのクラスを適用すればいいかをPeerが知ることができないからです。

効率はクラス階層がわかっている場合は改善することができ、 大抵の場合はわかっているでしょう。 この場合、クラスを以下のようにxml定義できます:

<table name="A"...>
  ...
  <column name="FOO" inheritance="single" type="CHAR" size="1"...>
    <inheritance key="B" class="B" extends="com.mycompany.project.om.A"/>
    <inheritance key="C" class="C" extends="com.mycompany.project.om.A"/>
    <inheritance key="D" class="D" extends="com.mycompany.project.om.B"/>
  </column>
</table>

上記では、クラス"A"を表わすためにNULL(あるいは他の値)を使用しています。 数値型のカラムをキーに使用することもできるかもしれません。 上記の方法を使用して、torqueは各クラスのコピーをキャッシュします。 したがって、Class.forNameはAPeerをメモリへ初期ロードする時にだけ実行されます。

デフォルトの振る舞いのオーバーライド

次の例はScarab(問題追跡システム)のものです。 Scarabでは、クラス階層定義が少数のテーブルに記述されています。これは階層の拡張に備えたものです。 このアレンジは、以下の拡張を用いることによって与えられます。 xml定義においては、クラスを決定する責務をもつカラムは属性inheritance="single"を使用してマークされます。

<table name="SCARAB_ISSUE_ATTRIBUTE_VALUE" idMethod="none"
        javaName="AttributeValue">
    <column name="ISSUE_ID" primaryKey="true" required="true"
            type="INTEGER"/>
    <column name="ATTRIBUTE_ID" primaryKey="true" required="true"
            type="INTEGER" inheritance="single"/>
    <column name="OPTION_ID" required="false" type="INTEGER"/>
...
    <foreign-key foreignTable="SCARAB_ISSUE">
        <reference local="ISSUE_ID" foreign="ISSUE_ID"/>
    </foreign-key>
    <foreign-key foreignTable="SCARAB_ATTRIBUTE">
        <reference local="ATTRIBUTE_ID" foreign="ATTRIBUTE_ID"/>
    </foreign-key>
    <foreign-key foreignTable="SCARAB_ATTRIBUTE_OPTION">
        <reference local="OPTION_ID" foreign="OPTION_ID"/>
    </foreign-key>
...
</table>

クラスを決定する責務をもつカラムが主キー、もしくは外部キーにもなっていることに注目すると興味深いかもしれません。 カラムがこの方法でマーキングされている場合、torqueはBaseAttributeValuePeer.getOMClassメソッドを生成します。 このメソッド中のコードはintegerのカラムからの情報を元にクラスを作成することを試みます。 これは明らかに間違っていますが、正しい情報を提供するようにオーバライド用のメソッドを与えてくれます。

AttributeValuePeerでは、メソッドを以下のようにオーバーライドします:

/**
 * SCARAB_ISSUE_ATTRIBUTE_VALUEテーブルの行の適切なクラス名を取得します
 */
public static Class getOMClass(Record record, int offset)
    throws Exception
{
    NumberKey attId = new NumberKey(record.getValue(offset-1 + 2).asString());
    Attribute attribute = Attribute.getInstance(attId);
    String className = attribute.getAttributeType().getJavaClassName();

    TurbineGlobalCacheService tgcs =
        (TurbineGlobalCacheService) TurbineServices
        .getInstance().getService(GlobalCacheService.SERVICE_NAME);

    String key = getClassCacheKey(className);
    Class c = null;
    try
    {
        c = (Class) tgcs.getObject(key).getContents();
    }
    catch (ObjectExpiredException oee)
    {
        c = Class.forName(className);
        tgcs.addObject(key, new CachedObject(c));
    }
    return c;
}

上記のメソッドでは、クラス情報を得るためにテーブルをまたがる外部キーを使用しています。 その後、各行のClass.forNameによる効率低下を回避するためにクラスをキャッシュします。(さらに、データセットが小さく、静的なものであるので、クラス階層の内容もキャッシュします。)