なぜ、リレーショナル・データベースへのオブジェクトのマップについての説明が必要なのでしょうか。これは、オブジェクトのパラダイムとリレーショナルのパラダイムとの「インピーダンスのミスマッチ」のためです。オブジェクトのパラダイムは、結合、連結、カプセル化などのソフトウェア・エンジニアリングの方針に基づいているのに対し、リレーショナルのパラダイムは、数学的な方針、特に集合論の方針に基づいています。2 つのベースは理論的に異なるため、長所や短所も異なります。その上、オブジェクトのパラダイムは、データと振る舞いの両方が割り当てられたオブジェクトからアプリケーションを作成することに焦点が置かれているのに対し、リレーショナルのパラダイムは、データの格納に焦点が置かれています。「インピーダンスのミスマッチ」は、これから説明するすばらしいアプローチがわかれば、効果を発揮します。つまり、オブジェクトのパラダイムでは、関係を介してオブジェクト間を移動するのに対し、リレーショナルのパラダイムでは、データを重複してテーブル内の列を結合します。このように基本的に異なるので、2 つのパラダイムの組み合わせは理想的とは言えませんが、予想される障害はわずかです。リレーショナル・データベースにオブジェクトをマップする成功の秘けつの 1 つは、両方のパラダイムとその違いを理解し、その知識に基づいてインテリジェントなトレードオフを行うことです。
ここでは、今日、開発者間でよく見られる一般的な誤解を解き、リレーショナル・データベースへのオブジェクトのマップに関する問題について具体的に説明します。金融、流通、軍事、通信、アウトソーシングの各業界における、小規模から大規模に渡るプロジェクトの開発経験をベースに紹介します。これらの方針は、C++、Smalltalk、Visual Basic、Java の各言語で作成されたアプリケーションに適用しました。
この項では、リレーショナル・データベースにオブジェクトを正しくマップするために必要な基本テクニックについて説明します。
クラス属性には、リレーショナル・データベース内の列にマップされないものと、複数の列にマップされるものがあります。重要なのは、すべての属性がパーシスタントなわけではない、ということです。たとえば、Invoice クラスには、計算の目的でインスタンスが使用するgrandTotal 属性がありますが、この属性はデータベースには保存されません。さらに、オブジェクト属性の中には、独立したオブジェクトもあります。たとえば、Course オブジェクトには、属性として、データベース内の複数の列にマップされるTextBook のインスタンスがあります (実際には、おそらくTextBook クラス自体が独自に 1 つ以上のテーブルにマップされることになります)。大切なことは、これは帰納的定義であるということです。属性は列にマップされないこともあれば、複数の列にマップされることもあります。また、複数の属性をテーブル内の 1 つの列にマップすることもできます。たとえば、米国の郵便番号を表すクラスには、それぞれ郵便番号の各セクションを表す 3 つの数字属性を割り当て、郵便番号全体は、住所テーブル内の 1 つの列として格納することができます。
リレーショナル・データベースにオブジェクトを保存する場合、継承の概念は、興味深い、別の角度から処理します (参考文献 の "Building Object Applications That Work" を参照)。この問題は基本的に、継承属性をパーシスタンス・モデル内で編成する方法を理解するということにつながります。この難しい問題を解決する方法は、システム設計に大きな影響が与える可能性があります。リレーショナル・データベースに継承をマップするための基本的なソリューションは 3 つあります。これらを理解するために、図 1 に示すクラスのマップのトレードオフについて説明します。簡潔にするため、クラスの属性すべてをモデル化しませんでした。また、シグニチャー全体のモデル化や、クラスのすべてのメソッドのモデル化も行いませんでした。
図1. 簡単なクラス階層の UML クラスのダイヤグラム
クラスはテーブルにマップしますが、たいていの場合は、直接マップするわけではありません。非常に簡単なデータベースを除き、クラスとテーブルは 1 対 1 でマップするわけではありません。次に、リレーショナル・データベースに継承構造を実装する 3 つのストラテジーについて説明します。
このアプローチでは、クラス階層全体を 1 つのデータ・エンティティーにマップし、そこに階層内のすべてのクラスの属性を格納します。図 2 に、このアプローチを使用した場合の図 1 のクラス階層のパーシスタンス・モデルを示します。テーブルの 1 次キーには、personOID 列が導入されたことに注意しましょう。整合性が確保され、データ・エンティティーにキーを割り当てる場合の (わたしが知っている中で) 一番良いアプローチを取るために、すべてのソリューションで OID (ビジネス上意味を持たない ID、代理キーとも言う) を使用します。
図 2. 1 つのデータ・エンティティーへのクラス階層全体のマップ
このアプローチの長所は、簡単であり、人の役割が変わったときにポリモアフィズム (多相性) がサポートされ、必要なすべての個人データが 1 つのテーブルに収められているので、随時レポート作成 (一般的にレポートを自分たちで作成する小規模のユーザー・グループで、特定の目的で実行されるレポート作成) も非常に簡単に行える、という点です。短所は、新しい属性をクラス階層に追加するたびに、新しい属性をテーブルに追加しなければならない点です。これにより、クラス階層内の結合性が向上します。1 つの属性を追加したときにミスをすると、そのミスは階層内のすべてのクラスに影響するだけでなく、新しい属性を取得したクラスのサブクラスにも影響します。また、データベース内で大量のスペースが無駄になる可能性もあります。行が学生である、先生であるか、または別のタイプの人かを示すobjectType 列を追加する必要もありました。これは、1 人につき役割が 1 つの場合にはうまくいきますが、1 人が複数の役割を持っている場合 (たとえば、学生と先生の両方を兼ねる場合) にはすぐに破綻が生じます。
このアプローチでは、各データ・エンティティーに、そのデータ・エンティティーが表すクラスの属性および継承属性の両方が組み込まれます。図 3 に、このアプローチを使用した場合の図 1 のクラス階層のパーシスタンス・モデルを示します。Student クラスとProfessor クラスは具象クラスなので、対応するデータ・エンティティーがありますが、Person クラスは抽象クラス (名前がイタリック体で示されていることからわかります) なので、対応するエンティティーはありません。各データ・エンティティーには、それぞれ独自の 1 次キーstudentOID およびprofessorOID を割り当てました。
図 3. 1 つのデータ・エンティティーへの各具象クラスのマップ
このアプローチの最大の長所は、単一のクラスについて必要なデータすべてが 1 つのテーブルだけに格納されるので、随時レポート作成を比較的簡単に行える点です。しかし、短所もいくつかあります。1 つ目は、クラスを変更する場合に、そのクラスのテーブルと、そのサブクラスのテーブルを変更しなければならない点です。たとえば、Person クラスに身長と体重を追加する場合は、その両方のテーブルを更新しなければなりません。これは、かなりの作業です。2 つ目に、オブジェクトが役割を変えた場合 (卒業生の 1 人を採用して先生にした場合) は、そのデータを適切なテーブルにコピーし、新しい OID を割り当てなければなりません。これもまた、大変な作業です。3 つ目に、複数の役割をサポートし、データ保全性を保持することが大変になります (できないのではなく、ただ、大変なだけです)。たとえば、学生と生徒の両方を兼ねる人の名前を格納する場所がありません。
このアプローチでは、クラスごとに 1 つのテーブル、つまり、OID である属性とそのクラスに固有の属性を作成します。図 4 に、このアプローチを使用した場合の図 1 のクラス階層のパーシスタンス・モデルを示します。personOID は 3 つのデータ・エンティティーに対する 1 次キーとして使用することに注意してください。図 4 の興味深い特徴は、Professor とStudent の両方のpersonOID 列に、2 つのステレオ・タイプ (Unified Modeling Language (UML) では認められないもの) が割り当てられている点です。個人的な考えでは、これは UML パーシスタンス・モデリング・プロファイルで処理しなければならない問題であり、このモデリング規則で変更が必要になることもあります (パーシスタンス・モデルについての詳細は、参考文献の "Towards a UML Profile for a Relational Persistence Model" を参照してください)。
図 4. 独自のデータ・エンティティーへの各クラスのマップ
このアプローチの主な長所は、オブジェクト指向の概念に最も適切に準拠している点です。オブジェクトに割り当てられた役割ごとに、適切なテーブルに単にレコードを入れるだけで、ポリモアフィズムは非常に適切にサポートされます。また、1 つのテーブルを変更したり追加したりするだけでよいので、スーパークラスを変更したり、新しいサブクラスを追加することも非常に簡単です。このアプローチには、短所もいくつかあります。1 つ目に、データベース内のテーブルの数が多くなります。実際には、各クラスごとに 1 つのテーブル (および、関係を保持するテーブル) が生成されます。2 つ目に、このテクニックの場合は複数のテーブルにアクセスしなければならないので、データの読み取り/書き込み時間が長くなります。この問題は、各テーブルを、クラス階層内の物理的に異なるディスク・ドライブ・プラッターに置くことでデータベースをインテリジェントに編成すれば 解決できます (ここでは、ディスク・ドライブ・ヘッドがそれぞれ別々に動作することを前提とします)。3 つ目に、データベース上の随時レポート作成は、必要なテーブルをシミュレートするビューを追加しない限り難しくなります。
それぞれのマッピング・ストラテジーにより、生成されるモデルかいかに異なるかに注意しましょう。3 つのストラテジー間の設計トレードオフを理解するために、図 5 に示すクラス階層の簡単な変更を考えてください。ここでは、Professor から継承したTenuredProfessor クラスが追加されています。
図 5. 初期クラス階層の拡張
図 6 に、クラス階層全体を 1 つのデータ・エンティティーにマップする、更新パーシスタンス・モデルを示します。このストラテジーに従ってモデルを更新するために行わなければならないことはほとんどありませんが、データベース上の無駄なスペースが増えるという問題が起きることに注意しましょう。
図 6. 1 つのデータ・エンティティーへの拡張階層のマップ
図 7 に、各具象クラスをデータ・エンティティーにマップした場合のパーシスタンス・モデルを示します。このストラテジーで必要なのは、新しいテーブルの追加だけでしたが、終身在職権を持つ先生に先生の昇進を追加したので、わたしたちとの関係を変更するオブジェクトの処理方法の問題 (生徒が先生になること) はさらに複雑になってしまいました。
図 7. データ・エンティティーへの拡張階層の具象クラスのマップ
図 8 に、3 つ目のマッピング・ストラテジー、つまり 1 つのデータ・エンティティーに 1 つのクラスをマップする場合のソリューションを示します。この場合には、TenuredProfessor クラスの新しい属性だけを含む新しいテーブルを追加する必要がありました。このアプローチの短所は、新しいクラスのインスタンスを利用する場合に複数のデータベースにアクセスしなければならない点です。
図 8. データ・エンティティーへの拡張階層のすべてのクラスのマップ
これまで説明してきたアプローチの中に、完全なものは 1 つもありません。それぞれ長所と短所があります。表 1 にその比較をまとめます。
表 1. 継承のマップ・アプローチの比較
| 考慮する要因 | 階層ごとに 1 つのテーブル | 具象クラスごとに 1 つのテーブル | クラスごとに 1 つのテーブル |
|---|---|---|---|
| 随時レポート作成 | 簡単 | 普通 | 普通/難しい |
| 実装の簡易性 | 簡単 | 普通 | 難しい |
| データ・アクセスの簡易性 | 簡単 | 簡単 | 普通/簡単 |
| 結合性 | 非常に高い | 高い | 低い |
| データ・アクセスの速度 | 高速 | 高速 | 普通/高速 |
| ポリモアフィズムのサポート | 普通 | 低い | 高い |
オブジェクトをデータベースにマップするだけでなく、オブジェクトに関連付ける関係も、後で復元できるようにマップしなければなりません。オブジェクトに関連付けることができる関係には、継承、関連、集約、構成の 4 つのタイプがあります。これらの関係を効率よくマップするには、それぞれのタイプの違い、一般的な関係の実装方法、多対多の関係専用の実装方法を理解しておかなければなりません。
データベースの観点からすると、関連、集約/構成の関係の違いは、オブジェクトが互いにどれくらい密接に結合されているかということだけです。集約/構成ではデータベース内の全体に対して実行することはすべて、ほとんど必ずそのパーツに対して実行する必要があるのに対し、関連ではそれは当てはまりません。
図 9 に、3 つのクラスを示します。そのうちの 2 つのクラスの間に簡単な関連があり、また 2 つのクラスが集約関係を共有しています (実際のところ、このモデル化には、集約よりも構成の方が適切だったようです)。(関連についての詳細は、参考文献の "Building Object Applications That Work" を参照してください) データベースの観点からすると、集約では、一般的に全体を読み込むにはパーツを読む込む必要があるのに対し、関連では、明らかに必ずしもその必要がない点で、集約/構成と関連は異なります。同じことは、データベースへのオブジェクトの保存と、データベースからのオブジェクトの削除にも当てはまります。また、これは通常、ビジネス・ドメインに固有ですが、経験からするとほとんどの環境に当てはまるようです。
図 9. 関連と集約/構成の違い
リレーショナル・データベース内の関係は、外部キーを使用して保持されます。外部キーは、別のテーブルのキーの一部である、またはそのキーに一致する、あるテーブル内の 1 つ以上のデータ属性です。外部キーを使用することで、あるテーブル内の行を別のテーブル内の行に関連付けることができます。1 対 1 の関係および 1 対多の関係を実装するには、あるテーブルのキーを別のテーブルに単に組み込むだけです。
図 10 に、3 つのテーブル、キー (OID)、およびそれらの間の関係を実装する場合に使用する外部キーを示します。最初に、Position データ・エンティティーとEmployee データ・エンティティーの間に 1 対 1 の関連があります。1 対 1 の関連とは、それぞれの多重度の最大数が 1 であるもののことです。この関係を実装するために、Employee データ・エンティティーで、属性positionOID をPosition データ・エンティティーのキーとして使用しました。このようにしたのは、この関連が単方向だからです。つまり、employee 行は、対応する position 行については認識しますが、その逆は成り立ちません。これが双方向の関連の場合には、Position にemployeeOID という外部キーを追加しなければなりません。2 つ目に、Employee とTask の間で、同じような方法を使用して多対 1 の関連 (1 対多の関連とも言う) を実装しました。唯一異なる点は、外部キーが関連の「多」の側にあったため、その外部キーをTask に追加しなければならなかったことです。
図 10. 簡単な人事データベースのパーシスタンス・モデル
多対多の関係を実装するには、関連テーブルの概念、つまり、リレーショナル・データベース内の 2 つ以上のテーブル間の関連を保持することだけを目的としたデータ・エンティティーが必要です。図 10 に、Employee とBenefit 間の多対多の関係を示します。図 11 に、関連テーブルを使用して多対多の関係を実装する方法を示します。リレーショナル・データベースでは、関連テーブル内の属性は、本来、関係に関連しているテーブル内のキーの組み合わせです。関連テーブルの名前は一般に、そのテーブルが関連するテーブルの名前の組み合わせ、またはそのテーブルが実装する関連の名前のいずれかです。この場合は、BenefitEmployee やhas よりもEmployeeBenefit の方が関連の性質を適切に反映していると思ったため、EmployeeBenefit を選びました。
図 11. リレーショナル・データベースへの多対多の関係の実装
図 11 の、多重度の応用に注意してください。図 12 に示すように、関連テーブルが導入されると、多重度が「交差する」、という規則があります。図 11 および 12 からわかるように、オリジナルの関連の多重度全体を保持するように、多重度 '1' は、常に外側のエッジに導入されます。オリジナルの関連では、1 人の従業員には 1 つ以上の手当が提供されていること、およびすべての手当は、ゼロ以上の従業員に提供されていることを示していました。図 11 を見ると、これは、関連を保持するために適切な場所に配置された関連テーブルにも当てはまります。
図 12. 関連テーブルの導入
大切なのは、関連クラスの形ではなく、ステレオ・タイプな "<<関連テーブル>>" を適用すること選んだことです。(点線で、関連クラスと、その関連クラスが記述する関連が結ばれています)。理由は次の 2 つです。1 つは、関連テーブルの目的は関連を実装することであるのに対し、関連クラスの目的は関連を記述することだからです。もう 1 つは、図 11 に示されている方法は、リレーショナル・テクノロジーを使用して採用する必要がある実際の実装ストラテジーを反映しているからです。
この報告書では、リレーショナル・データベースにオブジェクトをマップする場合の基本的な事柄について説明しました。ここで説明してきたステップに従えば、リレーショナル・データベースにオブジェクトを正しくかつ簡単に格納することができます。疑問がある場合は、ご遠慮なくscott.ambler@ronin-intl.com まで E メールをお寄せください。また、パーシスタンス・モデルの UML プロファイルへの入力の提供については、私の UML パーシスタンス・モデル・プロファイルの開発についてのワーキング・ページ を参照し、ご参加ください。
-
Building Object Applications That Work: Your Step-By-Step Handbook for Developing Robust Systems with Object Technology Scott W. Ambler 著 (New York:SIGS Books/Cambridge University Press)
-
Process Patterns: Building Large-Scale Systems Using Object Technology Scott W. Ambler 著 (New York:SIGS Books/Cambridge University Press)
-
The Object Primer 2nd Edition -- The Application Developer's Guide to Object-Orientation Scott W. Ambler 著 (New York:Cambridge University Press)
-
The Process Patterns Resource Page Scott W. Ambler 著
-
Towards a UML Profile for a Relational Persistence Model: Working Page Scott W. Ambler 著
Scott W. Amblerは、オブジェクト指向ソフトウェア処理の指導、アーキテクチャー・モデリング、およびEnterprise JavaBeans (EJB) 開発を専門とするコンサルタント会社である、Ronin International の社長です。彼は、オブジェクト指向開発に関する本を執筆あるいは共同執筆しています。最近刊行されたものとしては、この記事で要約された主題を詳しく論じた The Object Primer 2nd Edition などがあります。彼の連絡先はwww.ambysoft.com にあるサイトです。