レベル: 中級 Bruce Snyder (ferret@frii.com), Senior Software Engineer, DigitalGlobe
2002年 8月 01日 今日、ますます多くの企業プロジェクトで、Javaオブジェクトとリレーショナル・データのバインディングを多くのリレーショナル・データベース間で行うことのできる信頼性の高い方法が求められています。残念ながら、(私たちの多くは難しい方法を身に着けてしまったため) 企業内ソリューションは構築するのが難しく、長期間にわたる保守、拡張はさらに困難です。今回の記事では、Bruce Snyder氏が、100% Pure Javaテクノロジーに基づいて構築されたオープン・ソースのデータ・バインディング・フレームワークであるCastor JDOの使用の基本について説明します。
Castor JDO (Java Data Objects) は、オープン・ソースの100%Pure Javaデータ・バインディング・フレームワークです。1999年12月に初めてリリースされたとき、Castor JDOは、利用可能な最初のオープン・ソース・データ・バインディング・フレームワークの1つでした。以来、このテクノロジーは大きく進化し、今日では、他の多くのテクノロジー (オープン・ソースや商業ベースのものも) と組み合わせて使用され、Javaオブジェクト・モデルをリレーショナル・データベースやXML文書、LDAPディレクトリーにバインドします。
今回の記事では、Castor JDO使用の基本について学びます。まず、リレーショナル・データ・モデルとJavaオブジェクト・モデルについて取り上げ、これらのマッピングの基本について説明します。そのあとで、Castor JDOのいくつかの機能を説明します。製品を扱ったシンプルな例を使って、継承 (リレーショナルなものとオブジェクト指向のもの)、依存関係と関連関係、CastorのObject Query Language実装、Castorのショート・トランザクションとロング・トランザクションなど、基本的な事柄について学びます。この記事はCastorについて紹介するものなので、ここでは非常に簡単な例を使い、特定のトピックについて詳しく説明することはしません。記事の最後にこのテクノロジーについて簡単にまとめたものがありますので、今後の学習に役立ててください。
この記事では、オブジェクト・リレーショナル・マッピングに関する一般的な事柄は説明していませんのでご注意ください。オブジェクト・リレーショナル・マッピングの詳細については、参考文献のセクションをご覧ください。
Castor JDOとSun JDO仕様の相違について興味のある方は、「How does Castor differ from the Sun JDO spec?」をご覧ください。
概要
私の場合、データのモデリングでプロジェクトを開始する場合もあれば、オブジェクトのモデリングでプロジェクトを開始する場合もあります。この記事では、まずデータ・モデルから始めることにします。Castorには、製品の概念に関するデータ・モデルとオブジェクト・モデルを提示するいくつかのJDOの例が備わっています。この記事では、そうした例の1つを使用して、いくつかの異なる面について説明します。図1は、その例に対するデータ・モデルの実体関連 (ER) 図です。簡潔にするために、明示的な外部キーは含まれていません。しかし、テーブル間にはID参照がありますのでご注意ください。
 |
ソースの入手
Castor Projectプロジェクトのソース・コードは、ExoLabライセンスと呼ばれるBSDスタイルのライセンスによってJavaテクノロジー・コミュニティーにリリースされています。参考文献をご覧になり、Castor JDOをダウンロードしてください。
|
|
図1. Castor JDOデータ・モデルの例
図2は、例に対するオブジェクト・モデルのUMLクラス図です。JDOの例は、テーブルからオブジェクトへの1対1表現を提供しています。
図2. Castor JDOオブジェクト・モデルの例
CastorのJavaオブジェクトは、JavaBeansコンポーネントに非常によく似ています。つまり、オブジェクトには通常、プロパティーごとに1対のaccessorメソッドとmutatorメソッド (getterメソッド /setterメソッド) が含まれています (但し、直接アクセスするようにプロパティーがマップされていない限り)。さらに複雑な関係の場合は、他の目的のために追加のロジックを含めることができます。オブジェクト・リレーショナル・マッピングはたちまちひどく複雑なものとなる可能性があります。しかし、これらの例は本質的にはとても単純なものです。
リスト1は、Product オブジェクトのソース・コードを示しています。コードには、IDなどの製品テーブルの各列に対するプロパティーが含まれていることに注意してください。オブジェクト・リレーショナル・マッピングをご存じない方にとっては、IDのプロパティーが奇妙に思われるかもしれませんが、この構成は実際にはかなり一般的なもので、他のオブジェクト・リレーショナル・フレームワークでも使用されています。Castorは、内部でオブジェクトIDを使用して、オブジェクトを追跡します。さらに、関連するProductDetail オブジェクトとCategory には、2つのjava.util.Vector があります。また、このオブジェクトにはtoString() メソッドが含まれていることに注意してください。このメソッドは、アプリケーションをデバッグする際のロギングに役立ちます。
リスト1. Product.java
package myapp;
import java.util.Vector;
import java.util.Enumeration;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.Persistent;
public class Product implements Persistent
{
private int _id;
private String _name;
private float _price;
private ProductGroup _group;
private Database _db;
private Vector _details = new Vector();
private Vector _categories = new Vector();
public int getId()
{
return _id;
}
public void setId( int id )
{
_id = id;
}
public String getName()
...
public void setName( String name )
...
public float getPrice()
...
public void setPrice( float price )
...
public ProductGroup getGroup()
...
public void setGroup( ProductGroup group )
...
public ProductDetail createDetail()
{
return new ProductDetail();
}
public Vector getDetails()
{
return _details;
}
public void addDetail( ProductDetail detail )
{
_details.add( detail );
detail.setProduct( this );
}
public Vector getCategories()
{
return _categories;
}
public void addCategory( Category category )
{
if ( _categories.contains( category ) ) {
_categories.addElement( category ); category.addProduct( this );
}
}
...
public String toString()
{
return _id + " " + _name;
}
}
|
図2 では、ProductDetail オブジェクトとCategory オブジェクトでProduct との関係が異なっていることに注意してください。
- 1つの
Product は複数のProductDetail を持つことができます。これは1対多の関係です。
- 複数の
Product は複数のCategory オブジェクトを持つことができます。これは多対多の関係です。
addDetail() とaddCategory() のメソッドを使用して、それぞれのjava.util.Vector にオブジェクトを追加すると、それぞれの関係を管理することができます。
マッピング記述子の宿命
人生の鍵は自分の宿命をきちんと理解して受け入れることであると信じている人がいます。私も同じような悟りを開きました。Castor JDOを使用する際の鍵はマッピング記述子の適切な理解と実装だと。マッピング記述子は、リレーショナル・データベースとJavaオブジェクトのつながり (マップ) を提供します。マッピング記述子の形式がXMLであることにはそれなりの理由があります。Castor Projectの残りの半分はCastor XMLです (参考文献を参照)。Castor XMLは、JavaからXMLへの、およびXMLからJavaへの優れたデータ・バインディング・フレームワークを提供します。Castor JDOは、XML文書をJavaオブジェクト・モデルにアンマーシャルするCastor XMLの機能を利用して、マッピング記述子を読み取ります。
オブジェクトとプロパティーを要素と属性にマッピング
Javaオブジェクトはそれぞれ<class> 要素で表され、そのオブジェクトの各プロパティーは<field> 要素で表されます。さらに、リレーショナル・テーブル内の各列は<sql> 要素で表されます。リスト2は、上述したProduct オブジェクトのマッピングを示しています。リストを見ながら、このコードについてさらに詳しく説明しましょう。
リスト2. Productのマッピング
<class name="myapp.Product" identity="id">
<description>Product definition</description>
<map-to table="prod" xml="product" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="name" type="string">
<sql name="name" type="char" />
</field>
<field name="price" type="float">
<sql name="price" type="numeric" />
</field>
<!-- Product has reference to ProductGroup,
many products may reference same group -->
<field name="group" type="myapp.ProductGroup">
<sql name="group_id" />
</field>
<!-- Product has reference to ProductDetail
many details per product -->
<field name="details" type="myapp.ProductDetail" required="true"
collection="vector">
<sql many-key="prod_id"/>
</field>
<!-- Product has reference to Category with
many-many relationship -->
<field name="categories" type="myapp.Category" required="true"
collection="vector">
<sql name="category_id"
many-table="category_prod" many-key="prod_id" />
</field>
</class>
|
<class> 要素は、いくつかの重要な属性と要素をサポートします。たとえば、Product のマッピングは、identity 属性を使用して、オブジェクトのどのプロパティーがオブジェクトIDであるかを示します。また、<class> 要素は<map-to> 要素もサポートし、各オブジェクトがマップするリレーショナル・テーブルをCastorに示します。また、<field> 要素は属性をサポートします。
<field> と<sql> のすべての要素のマッピングにはtype 属性が含まれていることに注意してください。type属性は、オブジェクトとリレーショナル・データ・タイプの変換を行うために内部で使用されるTypeConvertor をCastorに示します。
関係の定義
「to-many」 (対多) 関係にはそれぞれtype属性の特別なケースが存在します。前述のように、Product の2つのjava.util.Vector が1対多と多対多の関係を処理します。1対多の関係は、details と呼ばれる<field> 要素に存在します。details要素の情報は、プロパティーがjava.util.Vector タイプのCollection であること、このCollection にProductDetail タイプのオブジェクトが含まれること、およびこのCollection が必須である (つまりヌル値になりえない) ことをCastorに示します。<sql> 要素は、<field> のオブジェクト・マッピングにprod_id と呼ばれるSQL列が含まれ、1対多の関係の識別にはこの列が使用されることをCastorに示します。
多対多の関係は、categories と呼ばれる<field> 要素に存在します。categories要素は、プロパティーがjava.util.Vector タイプのCollection であること、このCollection にCategory タイプのオブジェクトが含まれること、およびこのCollection が必須である (つまりヌル値になりえない) ことをCastorに示します。<sql> 要素は、この関係がcategory_prod と呼ばれる追加のテーブルを利用していることをCastorに示します。また、この<field> のオブジェクト・マッピングにprod_id と呼ばれるSQL列が含まれ、多対多の関係の識別には、この列とcategory_id が使用されることを示しています。
ProductGroup とProduct の間には、さらにもう1つの関係が存在します。この関係は、多くのProduct が同じProductGroup に関係があるので、1対多の関係です。この関係はProduct とProductDetail の間の1対多の関係と同じですが、その逆の関係 (多対1) もあり、マッピングでは関係の「to-one」 (対1) の側だけが表示されています。
Castorの継承
CastorはJavaの継承とリレーショナルの継承という2種類の継承を使用します。リスト3にはComputer のマッピングが含まれています。図1 を見ると、Computer はProduct の拡張です。
Javaオブジェクトが単に汎用の基本クラスを拡張するかまたはインターフェースを実装する場合は、継承をマッピング記述子に反映する必要はありません。しかし、Product もComputer もマップされたクラスであるので、継承を適切に反映するにはそれをCastorに示す必要があります。<class> 要素のextends 属性は、Computer とProduct の関係を示すのに使用されます。図1 では、comp (コンピュータ) テーブルにprod (製品) テーブルの列が含まれていないことに注意してください。むしろ、prodテーブルに基本情報が含まれており、compテーブルがそれを拡張します。
リスト3. Computerのマッピング
<class name="myapp.Computer" extends="myapp.Product" identity="id">
<description>Computer definition, extends generic
product</description>
<map-to table="computer" xml="computer" />
<field name="id" type="integer">
<sql name="id" type="integer" />
</field>
<field name="cpu" type="string">
<sql name="cpu" type="char"/>
</field>
</class>
|
依存関係と関連関係
Castorは、2つのオブジェクトの関係を依存関係か関連関係として区別し、2種類のそれぞれの関係に対して異なるライフサイクルを保持します。これまでは、独立 (あるいは関連) オブジェクトについてのみ説明してきました。独立オブジェクトとは、マッピング記述子の<class> 要素の中にdepends 属性を指定されていないオブジェクトのことです。独立オブジェクトでCRUD (create:作成、read:読み取り、update:更新、delete:削除) 操作を実行する場合は、そのオブジェクト上で直接実行することができます。
リスト4には、依存オブジェクトのProductDetail のマッピングが含まれています。<class> 要素にdepends 属性が含まれていることに注意してください。これは、すべての操作に関してProductDetail がProduct に依存していることをCastorに示しています。この場合、Product がマスター・オブジェクトで、ProductDetail が依存オブジェクトです。ProductDetail オブジェクトでCRUD操作を実行する場合は、そのマスター・オブジェクトからのみ行うことができます。つまりaccessorメソッドを使用して、まずProduct を取り出し、そしてProductDetail をたどります。各依存オブジェクトは、マスター・オブジェクトを1つしか持てません。
リスト4. ProductDetailのマッピング
<class name="myapp.ProductDetail" identity="id" depends="myapp.Product">
<description>Product detail</description>
<map-to table="prod_detail" xml="detail" />
<field name="id" type="integer">
<sql name="id" type="integer"/>
</field>
<field name="product" type="myapp.Product">
<sql name="prod_id" />
</field>
<field name="name" type="string">
<sql name="name" type="char"/>
</field>
</class>
|
オブジェクト・モデルでのクエリーの実行
Castorは、Object Query Language (OQL) に対するODMG 3.0仕様のサブセットの実装を提供します。OQL用の構文はSQLの構文に似ていますが、この構文によって、データベースに対して直接クエリーするのではなくオブジェクト・モデルに対してクエリーすることができます。これは複数のデータベースをサポートしている場合に強力な資産となります。CastorのOQL実装は、内部でOQLクエリーをデータベース用の適切なSQLに翻訳します。パラメーターはbind() メソッドを使用してクエリーにバインドされます。OQLクエリーのいくつかの簡単な例を以下に示します。
CastorのOQL実装では、クエリー全体で完全修飾オブジェクト名を使用しつづけるのではなく、オブジェクトに対する別名が使用できます。以下のクエリーでは、c がその別名です。
すべてのComputer に対してクエリーを行う場合は、以下のクエリーを実行します。
SELECT c FROM myapp.Computer c
|
IDが1234であるComputer に対してクエリーを行う場合は、まず以下を実行し、
SELECT c FROM myapp.Computer c WHERE c.id= $1 |
次に以下を実行します。
名前が特定の文字ストリングであるComputer に対してクエリーを行う場合は、まず以下のクエリーを実行し、
SELECT c FROM myapp.Computer c WHERE c.name LIKE $1 |
次に以下を実行します。
IDがIDリスト内にあるComputer に対してクエリーを行う場合は、まず以下のクエリーを実行し、
SELECT c FROM myapp.Computer c WHERE c.id IN LIST ( $1, $2, $3 ) |
次に以下を実行します。
query.bind( 97 )
query.bind( 11 )
query.bind( 7 ) |
Castor OQL実装の詳細はこの記事の範囲を超えるため、詳細については参考文献のセクションをご覧ください。
Castorのトランザクション
Castorの永続性操作は、トランザクションのコンテキスト内で行われます。しかし、Castorはトランザクション・マネージャーではありません。むしろ、トランザクションの原子性を利用してオブジェクトをデータベースに対して持続させる正にキャッシュ・マネージャーということができます。このセットアップによって、アプリケーションは、オブジェクト・グラフへの変更をより簡単にコミットしたりロールバックしたりできます。非管理環境で実行されるアプリケーションは、トランザクションを明示的にコミットまたはロールバックしなければなりません。管理環境で実行される場合は、アプリケーション・サーバーがトランザクションのコンテキストを管理できます。
ショート・トランザクションとロング・トランザクション
Castorの通常のトランザクションはショート・トランザクションです。Castorはまた、2つのショート・トランザクションで構成されるロング・トランザクションの概念も提供しています。典型的なWebアプリケーションを例にして、この2種類のトランザクションの違いを理解できます。データをデータベースから読み取り、ユーザーに表示し、さらにデータベースにコミットする必要のあるアプリケーションの場合、トランザクション時間が非常に長くなる可能性があると思われます (これはWebアプリケーションでは一般的)。しかし、データベースの書き込みロックをいつまでも保持することはできません。これに対処するために、Castorは2つのショート・トランザクションを利用します。たとえば、最初のショート・トランザクションが読み取り専用クエリーを使用してオブジェクトを具体化します。これによって、トランザクションをオープンにしたままでなくてもオブジェクトを表示することができます。ユーザーが変更を行うと、2番目のショート・トランザクションが開始され、update() メソッドが変更されたオブジェクトをトランザクションのコンテキストに入れます。リスト5は、ロング・トランザクションの例です。
リスト5. ロング・トランザクションの例
public LineItem getLineItem()
{
db.begin();
LineItem lineItem = ( LineItem ) db.load( LineItem.class,
lineItemNum, Database.ReadOnly );
db.commit();
return lineItem;
}
public void updateOrder( LineItem li )
{
db.begin();
db.update( li );
db.commit();
}
|
最初のショート・トランザクションと2番目のショート・トランザクションの時間間隔は任意であるため、ダーティー・チェックを実行して、オブジェクトがデータベースで変更されたかどうかを判断しなければならないでしょう。ダーティー・チェックは、ロング・トランザクションの間にオブジェクトが変更されていないことを確認するために使用されます。マッピング記述子の<sql> 要素でdirty 属性を指定することによって、ダーティー・チェックを有効にすることができます。これが適切に機能するように、各オブジェクトはタイム・スタンプを保持しなければなりません。Timestampable コールバック・インターフェースを実装する場合は、Castorは、最初のショート・トランザクションでタイム・スタンプを設定し、2番目のショート・トランザクションの間にそれを確認します。
結論
 |
データベース・サポート
Castor JDOの主な機能は、Javaオブジェクトをリレーショナル・データベースにバインドするためのAPIの提供です。Castor JDOは以下のデータベースをサポートします。
- DB2
- HypersonicSQL
- Informix
- InstantDB
- Interbase
- MySQL
- Oracle
- PostgreSQL
- SAP DB
- SQL Server
- Sybase
- 汎用 (汎用のJDBCサポート用)
追加のデータベースに対するサポートの追加はごく簡単です。詳細については参考文献をご覧ください。
|
|
今回の記事では、Castorを使用したリレーショナル・データ・モデルとJavaオブジェクト・モデルのマッピングの基本について説明しました。この記事で使用された例は、Castorソース・コードで現在提供されている例に厳密に従っています。ただし、こうしたシンプルな例では、Castorの幅広い機能を網羅していません。実際には、キー生成、遅延ロード、LRUキャッシュ、様々なロッキング / アクセス・モードなどの他の多くの機能がCastorでサポートされています。こうした機能やその他の多くの機能については、参考文献のセクションをご覧ください。
Castor JDOは、今日利用できる数多くのオープン・ソース・データ・バインディング・ソリューションの1つにすぎません。今回の記事で示されたように、Castorは、サポートするデータベースごとに異なるSQLコードとJDBCコードを保持することが必須の企業内のソリューションに対して優れた代替手段を提供します。さらにCastorは、コミュニティー・ベースのオープン・ソース・プロジェクトであるため、絶えず改善が行われています。このテクノロジーについて確認し、ぜひCastorコミュニティーにご参加ください。
参考文献
著者について  | |  | Bruce Snyder氏は、コロラド州の大都市デンバーに住んでいます。氏は、過去数年間ここでJ2EEと関連ソリューションを実装する様々な新興企業に勤めました。現在は、コロラド州ロングモントにある衛星画像 / 情報企業であるDigitalGlobeでシニア・ソフトウェア・エンジニアとして働いています。氏のJavaテクノロジーとJ2EEの経験は、システム設計やアーキテクチャーから実装やテストに至るまですべての分野に及んでいます。氏は、約2年間Castorとオブジェクト・リレーショナル・データ・バインディングに取り組んだ後、最近Castor JDOチームの主任開発者になりました。彼の連絡先はferret@frii.com です。
|
記事の評価
|