レベル: 中級 Ted Neward (ted@tedneward.com), Principal, Neward & Associates
2007年 3月 20日 データベース戦争は終わり、リレーショナル・データベースが勝ったと言われてから久しく時間が経ちました。しかし、この状況によってプログラマーの間に平和と繁栄が訪れたと信じた人達が、最近ではリレーショナル・データベースを使おうとせず、Java™ オブジェクトを支持するようになっています。このシリーズ記事では、人気のライターであり講演者である Ted Neward が、今日のリレーショナル・データベースに対するオブジェクト指向の代替手段である、db4o を深く掘り下げて紹介します。
私がプログラマーとして一人前になった頃には、データベース戦争はほとんど終わったように思えました。Oracle などのリレーショナル・データベース・ベンダーは、リレーショナル・モデルと、標準化されたクエリー言語である SQL に関して、説得力のある議論をしてきました。実際のところ、私はリレーショナル・データベースの直接の先祖である IMS や一般的なフラット・ファイルなどを長期間の保管用に使ったことはない、とほぼ自信を持って言うことができます。どうやらクライアント/サーバーの時代が定着したようでした。
そしてある日、私は C++ を知りました。そしてそれ以来、この時代にこの言語を知った他のとても多くの人達の場合と同じく、私の世界観も変わりました。私は、関数とデータに基づくプログラミング・モデルから、オブジェクトに基づくプログラミング・モデルに移行したのです。そうすると突然、開発者達は優雅なデータ構造の作成や「情報隠蔽 (information hiding)」などを議論しなくなり、代わって私達はまったく新しい流行語、つまりポリモーフィズムやカプセル化、継承などにワクワクするようになったのです。
 |
このシリーズについて
過去 10 年ほど、情報の保管と取得はほとんど RDBMS と同義語でしたが、最近それが変わり始めています。特に Java 開発者は、いわゆるオブジェクトとリレーショナルとのミスマッチに不満を募らせており、それを解決するためのソリューションにも我慢できなくなっています。そのため、また有効な代替手段が現れてきたことにより、オブジェクトのパーシスタンスと取得に対する新たな関心が生まれつつあります。このシリーズでは db4o の実用的な紹介をします。db4o はオープンソースのデータベースであり、今日のオブジェクト指向の言語やシステム、考え方を活用しています。db4o のホームページを訪れ、今すぐ db4o をダウンロードしてください。この記事で説明する例を追うためには、このダウンロードが必要です。
|
|
それと同様に、突然リレーショナル・データベースは盛りを過ぎたと思われるようになり、新しい種類のデータベースである、オブジェクト・データベースが好まれるようになりました。OODBMS は、C++ のようなオブジェクト指向言語 (あるいは成り上がり者の「いとこ」である Java プログラミング) と組み合わせることで、プログラミングの理想の姿になると思われました。
しかし、実際にはそうなりませんでした。OODBMS は 1990 年代の終わり頃に絶頂期を迎え、その後は次第に目立たなくなりました。それまで興奮と栄光に満ちていたものが、不明瞭で特殊なものになってしまったのです。データベース戦争の第 2 ラウンドは終わり、そしてまたしてもリレーショナル・データベースの勝利でした。(しかもこれは、大部分の RDBMS ベンダーが何らかの形でオブジェクトをサポートしているにもかかわらず起きた結果です。)
ただしこのシナリオにも、1 つだけ問題があります。開発者達が OODBMS に熱狂した理由のいくつかは、消え去りませんでした。その証拠が db4o の出現です。
オブジェクトとリレーション
オブジェクトとリレーショナルのミスマッチは学術講義の話題には最適ですが、実はこの問題を突き詰めると、エンティティー同士の対話動作に関してオブジェクト・システムとリレーショナル・システムとで視点が異なることに行き着くのです。表面的に見ると、オブジェクト・システムとリレーショナル・システムはうまく適合するように思えますが、よく調べてみると、両者の間にいくつか根本的な違いがあることがわかります。
まず、オブジェクトが ID に関して持つ感覚は暗黙的です (ID は隠された、あるいは暗黙の this ポインター、あるいは参照で表記されますが、これは本質的にはメモリー内での位置です)。一方リレーションが ID に関して持つ感覚は明示的です (ID はリレーションの属性から成るプライマリー・キーで識別されます)。第 2 に、リレーショナル・データベースはカプセル化を行い、データに対するクエリーなどの操作に関する、データベース全体に渡る実装を隠蔽します。一方オブジェクト (モジュロ、もちろんクラス定義の中でどのような実装継承が指定されているかは無関係) は、各オブジェクトに対して新しい動作を実装します。そしておそらく最も興味深い点は、リレーショナル・モデルはクローズド・モデルであるという点であり、どのような操作を行っても、その結果として別の操作の入力に適したタプル・セットが作成されます。これによって、何よりも、ネストした SELECT が可能になります。オブジェクト・モデルには、そうした機能、特に「部分オブジェクト」を呼び出し側に返す機能はありません。オブジェクトは「あるかないか」であり、RDBMS のように 1 つの表あるいは一連の表から全部の列、あるいは一部の列のみを返せるという機能は、OODBMS にはまったくありません。
要約すると、 (Java コードや C++、C# などの言語で実装される) オブジェクトの動作と、(SQLServer や Oracle、DB/2 など最近の RDBMS で実装される) リレーションの動作の間には、大きなギャップがあるということです。そしてそのギャップを埋めるのは、必然的にプログラマーの仕事になります。
マッピングが失敗する場合
これまで開発者達は、オブジェクトとリレーショナルのギャップを手動のマッピングで埋めようとしてきました (例えば JDBC を使って SQL 文を作成し、その結果をフィールドに入れる、など)。この方法に対して、他にもっと容易な方法がないかという当然の疑問が湧きます。多くの開発者はこの問題を、自動化されたオブジェクト・リレーショナル・マッピング用ユーティリティーあるいはライブラリー (Hibernate など) を使って解決しています。
しかし、たとえ Hibernate (あるいは JPA や JDO、Castor JDO、Toplink あるいはどのような ORM ツール) を使ったとしても、マッピングの問題がなくなることはなく、単に構成ファイルの中に問題が移動するだけです。しかも、不適切なツールを無理やり使っているという感覚が避けられません。例えば、適切に階層化された継承モデルを作成しようとする場合、そのモデルを 1 つあるいは一連の表にマップするためには、どれも醜悪な選択肢を比較検討して 1 つを選ばなければなりません。クエリーのパフォーマンスと通常のフォームに違反することのどちらを重視するかをめぐって、どこかの時点で DBA と開発者が戦う羽目になります。
ここでの問題は、(Martin Fowler あるいは Eric Evans らの本に書かれているようなスタイルの) リッチなドメイン・モデルを構築したとしても、既存のデータベース・スキーマと一致させるためにそのモデルを変更するか、あるいはオブジェクト・モデルをサポートする操作を実行するためにデータベース機能を制限するか、あるいはその両方が必要であるとしたら、そうしたモデルに興奮することはできない、ということです。
では、何も妥協する必要がないとしたらどうでしょう。
db4o: OODBMS の再登場
db4o ライブラリーは最近 OODBMS の世界に登場してきたものであり、新世代のオブジェクト開発者の間に「純オブジェクト・ストレージ」の概念を復活させています。(実際、最近はレトロがホットと言われています。) db4o の使い方の感覚をつかむために、個々の人間を表現する次のような基本的なクラスを考えてみてください。
注意: まだ db4o をダウンロードしていない人は、ここでダウンロードしてください。このシリーズのこれから先の説明を追うためには (少なくともコードをコンパイルするためには)、db4o が必要です。
リスト 1. Person クラス
package com.tedneward.model;
public class Person
{
public Person()
{ }
public Person(String firstName, String lastName, int age)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() { return firstName; }
public void setFirstName(String value) { firstName = value; }
public String getLastName() { return lastName; }
public void setLastName(String value) { lastName = value; }
public int getAge() { return age; }
public void setAge(int value) { age = value; }
public String toString()
{
return
"[Person: " +
"firstName = " + firstName + " " +
"lastName = " + lastName + " " +
"age = " + age +
"]";
}
public boolean equals(Object rhs)
{
if (rhs == this)
return true;
if (!(rhs instanceof Person))
return false;
Person other = (Person)rhs;
return (this.firstName.equals(other.firstName) &&
this.lastName.equals(other.lastName) &&
this.age == other.age);
}
private String firstName;
private String lastName;
private int age;
}
|
一般的なクラスがそうであるように、Person クラスも特に目立ったところがありません。むしろ単純です。しかしこのクラスはやがて、例えば配偶者や子供を持つなど、より興味深いものとなり、オブジェクトのようなプロパティーと機能を持つようになると想定しておくのは妥当なことです (こうしたさまざまな内容については今後のコラムの中で具体的に説明する予定ですが、ここでは概要にとどめることにします)。
Hibernate ベースのシステムで、この Person クラスのインスタンスをデータベースの中に持つためには、下記のように、さらにいくつかのステップが必要です。
- データベースに対して型を記述したリレーショナル・スキーマを作成する必要があります。
- データベースの列と表をドメイン・モデルのクラスとフィールドにマップするマッピング・ファイルを作成する必要があります。
- コードの中で、Hibernate を使ってデータベースへの接続 (Hibernate の用語ではセッション) を開く必要があり、またオブジェクトを保管し、フェッチするために、Hibernate の API を使う必要があります。
db4o では、これらを驚くほど単純に行うことができます (リスト 2)。
リスト 2. db4o で INSERT を実行する
import com.db4o.*;
import com.tedneward.model.*;
public class Hellodb4o
{
public static void main(String[] args)
throws Exception
{
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
Person brian = new Person("Brian", "Goetz", 39);
db.set(brian);
db.commit();
}
finally
{
if (db != null)
db.close();
}
}
}
|
そして、これで終わりです。スキーマ・ファイルを生成する必要もなく、マッピング構成を作成する必要もなく、単純にクライアントを実行し、それが終了したら、persons.data に保管された新しい「データベース」に対するローカル・ディレクトリーをチェックするだけです。
保管された Person を取得するための方法も、ある面で、オブジェクト・リレーショナル・マッピング・ライブラリーの動作と似ています。つまりオブジェクトを取得するための最も単純な方法は、サンプルごとにクエリーを実行する方法です。db4o に対して、単純に同じ型のプロトタイプ・オブジェクト (フィールドはクエリーに使用する値に設定されています) を提供すると、その基準に一致する一連のオブジェクトが返されます (リスト 3)。
リスト 3. db4o で SELECT を実行する (バージョン 1)
import com.db4o.*;
import com.tedneward.model.*;
public class Hellodb4o
{
public static void main(String[] args)
throws Exception
{
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
Person brian = new Person("Brian", "Goetz", 39);
Person jason = new Person("Jason", "Hunter", 35);
Person brians= new Person("Brian", "Sletten", 38);
Person david = new Person("David", "Geary", 55);
Person glenn = new Person("Glenn", "Vanderberg", 40);
Person neal = new Person("Neal", "Ford", 39);
db.set(brian);
db.set(jason);
db.set(brians);
db.set(david);
db.set(glenn);
db.set(neal);
db.commit();
// Find all the Brians
ObjectSet brians = db.get(new Person("Brian", null, 0));
while (brians.hasNext())
System.out.println(brians.next());
}
finally
{
if (db != null)
db.close();
}
}
}
|
これを実行すると、2 つのオブジェクトが取得されることがわかります。
しかし・・・!
私が db4o をあからさまに「えいこひいき」しているという批判を受ける前に、db4o に対する一般的な反対意見をいくつか紹介しましょう。
-
db4o は、Oracle や SQLServer、DB2 にはとても及ばない。
- 確かに、そのとおりです。db4o はむしろ MySQL や HSQL と比較すべきものですが、多くのプロジェクトには十分対応可能です。もっと重要なこととして、db4o の開発者達はオーバーヘッドに注目しています。オーバーヘッドの点から、db4o は小規模環境や組み込み環境に最適なのです。(また、ここで示した例は単純なデモであることを忘れないでください。小さなデモではわからないかもしれませんが、他のツールと比較しての潜在機能はどうあれ、db4o はここで説明した内容をはるかに超える機能を持っているのです。)
-
JDBC を使って db4o に対してクエリーを実行できない。
- これも、そのとおりです。ただし db4o チームは、オブジェクト・データベースに対して SQL 構文を使える JDBC ドライバーを作成することを考えました。つまり一種の「リレーショナル・オブジェクト・マッピング」です。(しかし db4o チームは、それを作りませんでした。実際にはそれが必要ないと思われ、噂によるとパフォーマンスが非常に悪かったようです。) 重要なことは、実装にはオブジェクト、つまり POJO 以外、何も使わないという点です。リレーションを保管しないのに、なぜ SQL を使う必要があるのでしょう。
-
他のプログラムは、どのようにしてデータにアクセスするのか。
- それは状況によります。もし「他のプログラム」が他の Java コードのことを指しているのであれば、ここで示したように、単純にそうした他のプログラムでの Person クラスの定義を使い、それらを ObjectContainer に渡せばよいのです。OODBMS では、クラス定義自体がスキーマの役割を果たすため、Person オブジェクトをフェッチするために他のツールは必要ありません。しかし、もし「他のプログラム」が他の言語を指すのであれば、話は少し面倒です。 db4o がサポートしない言語 (C++ や Python など) では、Java コードから作成できる手段以外の方法では、データにアクセスすることは基本的にできません。db4o は C# などの .NET 言語で利用でき、db4o のデータ・フォーマットは両者の間で互換性があるため、Java オブジェクトを、そのオブジェクトと同じように定義された .NET クラスで利用することができます。そして、もし「他のプログラム」が、データベースとの対話動作に SQL や標準のコール・レベル・インターフェース (ODBC あるいは JDBC など) を使うレポート・ツールを指すのであれば、db4o は (あるいはどのような OODBMS も) おそらく適切な選択肢ではありません。ただし、現在 OODBMS ではレポートが利用できないと心配する読者がいたら、安心してください。多くの製品やプロジェクトが、正にそのために現れつつあります。さらに、db4o は、いわゆる「複製」をサポートしています。そのため db4o のインスタンスは、db4o 独自のストレージ・フォーマットから RDBMS へデータを複製できるのです。
-
db4o のデータはファイルである。
- この特定の場合について言えば、そのとおりです。ただし db4o は、軽量のクライアント/サーバー・オプションを含めて、データの保管場所や方法に関して非常に柔軟です。しかし、もし完全機能の RDBMS で実現されるような冗長性を期待するのであれば、db4o はそれほどの機能は持っていません (ただし他の OODBMS は実際にそうした機能を提供します)。
-
先ほどの例を再度実行すると、同じものが複製される (実際、この例を実行するたびに同じものが複製されます)。
- ここで改めて、オブジェクト・データベースとリレーショナル・データベースとを区別する元となる最初の興味深い「性質」、つまり ID について説明する必要があります。先ほど触れたように、オブジェクト・システムの ID は、Java オブジェクトがメモリー内で自分自身を識別するために使用する、暗黙的な「this」参照によって与えられます。一方オブジェクト・データベースでは、これは OID (object identifier) として知られており、OODBMS では、この OID がプライマリー・キーの役割を果たすのです。 新しいオブジェクトを作成し、それをデータベースの中に「設定」すると、この新しいオブジェクトには何も OID が関連付けられておらず、従ってそのオブジェクト独自の固有 OID 値を受け取ります。この ID は、RDBMS がすべての INSERT に対して (シーケンス・カウンターあるいは自動インクリメント・フィールドの形で) プライマリー・キーを作成する場合と同じように、複製されます。言い換えると、プライマリー・キーに関して OODBMS は RDBMS と同じように動作しますが、プライマリー・キーそのものは、従来の RDBMS (あるいは RDBMS に慣れたプログラマー) が考えるような種類のものではありません。
つまり db4o は、ある特定の問題群を解決するように見えますが、あらゆるパーシスタンス問題をすべて解決するソリューションとはなり得ません。実際、このことが、最初から db4o を他の OODBMS の選択肢とはまったく異なるものにしているのです。したがって、実稼働中の IT スタッフに対して、リレーショナル・データベースへの投資を完全に放棄すべきだと思わせるような種類のものではないのです。
まとめ
今後も当分の間、データのストレージと操作のための頼るべきツールとして、従来の集中型リレーショナル・データベースに置き換わるものはないでしょう。リレーショナル・データベース用に、とても多くのツールが長年に渡ってデフォルトとして確立されており、また非常に多くのプログラマーが、「データベースを常に扱う」ということの繰り返しに慣れきって作業していることからも、それは明らかです。実際 db4o は、技術的にそうした役割の RDBMS に挑戦するために設計され、位置づけられたものではありません。
しかし、「サービス指向」コミュニティーが構築を熱望している複数階層で疎結合の世界において、OODBMS を考え始めると、興味深いことが起こります。もし、コンポーネント (あるいはサービス、階層など、いまどきの呼び方はどうあれ) の間における真の疎結合が目標であるならば、OODBMS における唯一の結合は、サービスの呼び出し側とそのサービスの公開 API (あるいは見方によっては XML タイプ) との間の結合のみです。データ型もなく、公開されるオブジェクト・モデルもなく、共有データベースもありません。つまりパーシスタンスの選択肢は、実装の詳細次第であり、広範で多様なシナリオにおいては、利用可能なパーシスタンスの選択肢は 1 桁大きくなるのです。
参考文献 学ぶために
製品や技術を入手するために
- オープンソースでネイティブの Java プログラミングと .NET のためのデータベース、db4o をダウンロードしてください。
著者について  | |  | Ted Neward は、Neward & Associates の代表として、Java や .NET、XML サービスなどのプラットフォームに関するコンサルティング、助言、指導、講演を行っています。彼はワシントン州シアトルの近郊に在住です。 |
記事の評価
|