[技術情報]
今日から始めるオブジェクト指向データベース 〜教えてロバート Step.2〜 |
株式会社オージス総研
・目次
ES事業部PDチーム
国正@Objyチーム
with
Objyチームメンバ
6.前回のおさらい
7.実装すべし
7.1.何処が違うの?
7.2.関連や継承
7.3.データベースとローカルキャッシュの同期
8.格納すべし
8.1.Objectivity/DBの階層構造
8.2.くらすたぁ
9.次回予告
6.前回のおさらい
オブジェクトの広場愛読者の皆様、「今日から始めるオブジェクト指向データベース 〜教えてロバート〜 (略して『おしロバ』)」2ヶ月ぶりの連載です。
前回ではStone君がJavaを使って顧客を管理するためのアプリケーションを作ろうと しているところに、Robert氏が現れてオブジェクト指向データベースの採用を勧める、 というシナリオに沿ってオブジェクト指向データベースの特徴について、紹介させて 戴きました ( バックナンバーはこちら)。 オブジェクト指向データベースのイメージは、つかめていただけましたか? さて、今回は前回の最後にObjectivity for Javaのインストールや、サンプルソースを使った操作方法を紹介したので、実際のコーディング方法について紹介していきたいと思います。案内人は(やっぱり)あの二人に頼みましょうか・・・
7.実装すべし
◆◇ 7.1 何処が違うの? ◇◆Stone(以下す)「インストール完了っと。次はコーディングだね。まずはエンティティとなる商品クラスからっと・・・(カキカキ)、はい、出来ました。」
Robert(以下ろ)「どれ、1つ見せてもらおうかいな。」// --- Product.java ---
public class Product{ private String name; public Product(){ } public void setName(String name){ this.name = name; } public String getName(){ return this.name; } }ろ「(うわっ ごっつシンプルやなぁ・・・)せやな。 通常のJavaのクラス定義やとこれでOKやね。でも、このオブジェクトを Objectivity/DBに格納するためには、クラス定義にちょっと細工が必要になんねん。ちょっとええですか・・・(カキカキ)」// --- PersistentProduct.java ---
import com.objy.db.app.*; public class PersistentProduct extends ooObj{ private String name; public PersistentProduct(){ } public void setName(String name){ markModified(); this.name = name; } public String getName(){ fetch(); return this.name; } }ろ「まずはインポートするパッケージやねんけど、(*1)
をインポートする必要があるねん(*1)。
com.objy.db.app.* : Objectivity for Javaをアプリケーションから利用するためのクラスやインタフェース
で、ユーザが定義したクラスをObjectivity/DBに格納するためには、ooObjクラスを継承させて、このクラスのインスタンスは永続化されますよって明示的に宣言する必要があるんですわ(*2)。 この時に注意しないとあかんのは、どんな属性の値も永続化され るわけやなくて、永続可能な属性の型が決められているということ(*3)。
後は、fetch()やmarkModified()といったooObjクラスが定義するメソッドを、setメソッドやgetメソッドで呼び出す必要があるんやけど、まぁ属性の値を参照するときはfetch()、更新する場合はmarkModified()をそれぞれ呼び出すって感じで、おまじない的に組み込んでもらった方がええやろね。実際には、データベースに格納されているオブジェクトと、ローカルキャッシュ上に存在するオブジェクトの同期をとるためのメソッドなんやけど、詳しくは後で 説明するから(ここ)、興味があったら読んでみてや。」
す「ふーん。他のODB製品も大体同じような方法で クラスに変更を加えるのかな?」
ろ「そこは製品によってまちまちやね。例えば Javaのコンパイラによって出力されたclassファイルを、ポストプロセッサに かけて、データベースに格納できるようにする方法もあるようやね。」
その他に、Objectivity for Javaが提供するパッケージとしては、以下のもの があります。
com.objy.db.* : 例外やサポートクラス com.objy.db.iapp.* : 永続化のためのイン タフェース com.objy.db.util.* : 永続コレクションク ラスや、ユーティリティ的なクラス com.objy.db.ejb.* : グローバルトランザク ションをサポートするためのクラス(R7.0以降で提供)
(*2)
このように、ooObjクラスを継承するなどしてObjectivity/DBに格納する事が出来るクラスを『永続化可能クラス』と呼びます。
(*3)
例えば、以下のような型が永続化可能クラスの属性として指定する事が出来ます。
Javaプリミティブ型 : byte,short,int,long,float,double,boolean,char Javaの文字列クラス : String,StringBuffer Javaの日時クラス : java.util.Date,java.sql.Date,java.sql.TIme,java.sql.Timestamp 永続可能クラス : ooObj及び、ooObjを継承するなどして永続化可能となったクラス、永続コレクション等 上記クラスの配列 :
◆◇ 7.2 関連や継承 ◇◆ す「じゃ続いて他のクラスも実装してみよっと・・・(カキカキ)ん、注文が複数の明細を持つような関連をコーディングするときは、なにか工夫する必要はあるのかな?とりあえず書いてみたけど・・・」
// --- PersistentOrder.java ---
import com.objy.db.app.*; public class PersistentOrder extends ooObj{ PersistentDetail[] details; PersistentCustomer customer; ・・・ public PersistentOrder(){ } ・・・ }
// --- PersistentDetail.java ---
import com.objy.db.app.*; public class PersistentDetail extends ooObj{ ・・・ public PersistentDetail(){ } ・・・ }
ろ「それでかまへんよ。関連先が永続化可能クラスであれば、そのクラスや、クラスの配列を属性に持たせる事ができるねん。他の関連の実装方法として、Objectivity for Java が提供するリレーションクラスや、永続コレクションクラスを利用することで、いろいろと便利な機能が利用できるんやけど、まぁ最初はこれでええとして、あとでじっくり改善していこか。」
す「次は、顧客と法人顧客だね。顧客はいいんだけど、顧客を継承している法人顧客は・・・。顧客がooObjクラスを継承しているから、特に何もしなくていいか。」
// --- PersistentCustomer.java ---
import com.objy.db.app.*; public class PersistentCustomer extends ooObj{ ・・・ public PersistentCustomer(){ } ・・・ }
// --- PersistentCustomer_Legal.java ---
public class PersistentCustomer_Legal extends PersistentCustomer{ ・・・ public PersistentCustomer_Legal(){ } ・・・ }
ろ「そんなところやね。今一度クラス図で確認すると こんな感じか。」
![]()
Fig.6 サンプルモデル(ooObjクラスを付加)
す「ooObjクラスって、設計の段階からクラス図に反映させた方がいいの?」
ろ「せやなぁ、実際に開発を行う人たち次第やろうけど、明示的にしておく事で、クラス図を一目見ただけで、どのクラスが永続化されるかわかるってところは便利なんかもしれんな。」
◆◇ 7.3 データベースとローカ ルキャッシュの同期 ◇◆
す「なるほどね。確かにそうかもしんないね。でもさ、アクセスメソッド内部でfetch()やmarkModified()を呼び出す意味、っていうかそれらのメソッドが何のために必要なのかわからないんだけど。」
ろ「うーん。この辺はかなりディープな内容やから無理に説明する必要もないんやけど・・・よっしゃせっかくやから、ざっくり説明したろ!
前にも説明したけど、Objectivity/DBではデータベース内に格納されているオブジェクトを、ローカルキャッシュ内に読み込んで、読み込んだオブジェクトに対してアプリケーションが値を参照したり、更新したりするわけやけど、Objectivity/DBの基本設計として、アプリケーションから、オブジェクトの呼び出し(*5)が行われた際には、そのクラス定義情報だけしか読み込まないようになってんねん。ま、キャラメルひろたら箱だけ状態やな。」
す「えっそうなの?せっかくオブジェクトを取り出したのに、状態が反映されてないっておかしな話だね。じゃぁオブジェクトの値を取ってくるのはいつなの。っていうか何でそんな仕組みになってんの?」
ろ「まず、そんな仕組みを取る理由についてやけど、アプリケーションから呼び出されるオブジェクトが、他のオブジェクトと関連をもたないような、単独で存在するようなオブジェクトやったら、呼び出された時点で、持っている情報も一緒にローカルキャッシュに持ってくるのもええねん。でも、もし呼び出されたオブジェクトが他のオブジェクトと関連を持ってて、さらにそのオブジェクトが関連するオブジェクトを持つようになってたらどないや?」
す「呼び出されたオブジェクトだけじゃなく、関連するオブジェクトもゴッソリ取ってくることになるのかな。」
ろ「せや、当然ゴッソリとってきたオブジェクトの中には、今すぐ使う必要が無いオブジェクトも含まれてる可能性があるわけや。特にツリー構造になっているオブジェクトのルートだけを取得したい場合なんて、もう悲惨やで。」
す「無駄が多いってわけか・・・」
ろ「なにより、オブジェクトを取得するときの通信コストも無視できへんし。そんな背景もあって、Objectivity/DBでは、実際に対象のオブジェクトの中身を取ってくるタイミング、つまり”データベースに格納されているオブジェクト”と”ローカルキャッシュに存在するオブジェクト”の同期をとるタイミングを、オブジェクトの呼び出しとは違うところで行ってるわけやねん。で、どのタイミングで値を取ってくるのがええかというと・・・いつがええと思う。」
す「そうだなぁ。実際に値を参照するときには当然値が入っていないとまずいから、参照や更新を行う直前かなぁ。参照や更新の直前・・・あっそうか。fetch()やmarkModified()は、実際にオブジェクトの中身を取って来るためのメソッドなんだね。」
ろ「そういうことや。オブジェクト指向言語でクラスを実装するときは、まぁ100%そうでないにしろ、クラスの属性にアクセスするためのインタフェースとして、getメソッドや、setメソッドを定義するやろ。だから、それらメソッドの内部で定義しておけば、実際にオブジェクトが持つ値を参照や更新をしようとした時、属性にアクセスする直前で、同期が取られる事が保証できるわけや。その際には、対象のオブジェクトの中身だけ取ってきて、参照先のオブジェクトの中身は取ってけえへんから、無駄な通信コストを避ける事が出来るわけや。ちなみにfetch()とmarkModified()では、データベースからオブジェクトを取ってくる時に、データベース側に要求するロックの種類がちがうねん。fetch()のときはリード・ロック、markModified()の時はアップデート・ロックがそれぞれ対応しているわけや。ついでに言っとくと、一度fetch()やmarkModified()でオブジェクトを呼び出したら、以降fetch()やmarkModified()の呼び出しがあっても処理は無視されるから、そのオーバヘッドについては気にせんでええねん。」![]()
Fig.7 オブジェクトの呼び出し
(*5) ![]()
Fig.8 オブジェクトの情報を取得
オブジェクトの呼び出しは、Objectivity for Javaが提供するAPIにより行う事が出来ます。その方法については次回の連載で説明します
8.格納すべし
◆◇ 8.1 Objectivity/DBの階層 構造 ◇◆す「次は、エンティティのクラス達を操作するアプリケーションの実装だね。」
ろ「ちょいまち、その前にObjectivity/DBのストレージ階層について知っとかなあかんねん。下の図を見てや。」
![]()
Fig.9 Objectivity/DBのストレージ階層
ろ「それぞれの階層の役割は、
フェデレートデータベース : データベースのカタログ、スキーマ情報を管理 データベース : アプリケーションが利用する永続データ(オブジェクト)を格納 コンテナ : ロックの単位として、永続データ(オブジェクト)をまとめたストレージ ベーシックオブジェクト : 永続データ(オブジェクト)そのもの
というところや。ちなみにブートファイルは、フェデレートデータベースと接続するために必要な情報がかかれているファイルで、アプリケーションや、Objectivity/DBのツールからフェデレートデータベースを操作しようとするときは、必ずブートファイルを利用して接続する必要があるんや。ま、逆に言えば、ブートファイルさえ取得できれば、フェデレートデータベースや、データベースがどのホスト上にあるのかを意識せんでええわけやね。」
す「コンテナって、ロックの単位としてデータを格納するわけか。オブジェクトをまとめた単位という意味では、RDBのテーブルと似ているようだけど。考え方として"コンテナ=テーブル"でいいの?」
ろ「それは、YESとは言いにくいなぁ。ていうのも、RDBにおけるテーブルというのは、ある特定の種類のデータを格納するための入れ物という意味合いが強くて、例えば顧客テーブルなら顧客情報、商品テーブルなら商品情報を入れる事が一番の目的になるわけや。そういう意味やと、一般的に言われている”クラス=テーブル”のほうがしっくりくるねん。で、コンテナは、ロックの単位という意味合いが強くて、あるコンテナ内のオブジェクトに対してロックがかかれば、同じコンテナにある全てのオブジェクトにロックがかかるわけやけど、必要であれば、あるコンテナに、顧客オブジェクトと商品オブジェクトが混在するという状況も可能なわけや。」
す「コンテナがロックの単位だとすると、1つのコンテナにオブジェクトをポイポイ格納すると、大変な事になるわけか。」
ろ「その辺はデータベースの設計者の腕の見せ所やね。例えば、ロックの衝突を避けて並行性をあげるために、同じ種類のコンテナを複数作って、適当なディスパッチングルールで格納するオブジェクトを分散させれば、それだけ並行性の向上が期待できるかも知れへんしね。」
◆◇ 8.2 くらすたぁ ◇◆ ろ「さて、いよいよオブジェクトを操作する側の実装やけど、今までJavaアプリケーションからJDBC経由でRDBと接続した事はあんの?」
す「うーん。だいぶ昔にした記憶が・・・。確か、DriverManagerからConnectionオブジェクトを取得して、Statementオブジェクトを生成して、検索系ならexecuteQueryメソッド、更新系ならexecuteUpdateメソッドをStatementオブジェクトに対して呼び出すんだよね。検索系の場合はexecuteQueryメソッドの戻り値をResultSetオブジェクトで受け取って、値の解析を行うんだっけか。」
ろ「そうそう、ま、想像はつくやろうけど、Objectivity/DBのようなオブジェクト指向データベースを使った場合は、JDBCを利用するわけや無く、直接的にオブジェクトを操作するわけやから、手順は全く異なってくんねん。ま、とりあえず、このソースコードを見てみてや」// --- TestApp.java ---
import com.objy.db.*; import com.objy.db.app.*; public class TestApp{ public static void main(String[] args){ Connection connection; try{ // ブートファイルを指定して、フェデレートデータベースと接続する connection = Connection.open("BOOTFILE",oo.openReadWrite); } catch (DatabaseNotFoundException e){ System.out.println("Federated database not found."); return; } catch (DatabaseOpenException e2){ System.out.println("Federated database already opened."); return; } // データベースとの対話に利用するセッションオブジェクトの生成 Session session = new Session(); // フェデレートデータベースの参照を取得 ooFDObj fd = session.getFD(); try{ // トランザクション開始 session.begin(); // フェデレートデータベースにTEST_DBというデータベースが存在すれば参照を取得 // 存在しなければ新たに生成して参照を取得 ooDBObj db; if (fd.hasDB("TEST_DB")) { db = fd.lookupDB("TEST_DB"); } else { db = fd.newDB("TEST_DB"); } // データベース(TEST_DB)にTEST_CONTというコンテナが存在すれば参照を取得 // 存在しなければ新たに作成してデータベースに追加し、参照を取得 ooContObj cont; if (db.hasContainer("TEST_CONT")) { cont = db.lookupContainer("TEST_CONT"); } else { cont = new ooContObj(); db.addContainer(cont, "TEST_CONT", 1, 10, 100); cont = db.lookupContainer("TEST_CONT"); } // PersistentProductのインスタンスを生成 PersistentProduct product = new PersistentProduct(); // くらすたぁ!! cont.cluster(product); // トランザクションをコミット session.commit(); }catch(Exception e){ System.out.println("Exception !!"); // トランザクションをアボート session.abort(); } } }
ろ「ほな、最初から見ていこか。
// ブートファイルを指定して、フェデレートデータベースと接続するコメントの通り、ブートファイルを指定して、フェデレートデータベースと接続する命令や。データベースを操作するアプリケーションで、最初に一度だけ呼び出しておけばOKや。また、2つ目の引数で、読み取り専用の接続か、更新可能な接続かを指定することができるで。ブートファイル、フェデレートデータベースはあらかじめObjectivity/DBが提供するoonewfdツールを使って作るんやけど、前回連載の「4.3 データベースの中身を閲覧する」を参考にしてや。
connection = Connection.open("BOOTFILE",oo.openReadWrite);
// データベースとの対話に利用するセッションオブジェクトの生成このSessionクラスのオブジェクトを使ってアプリケーションからトランザクションの制御をおこなえるんや。他にも、前々から言っているクライアントのキャッシュ領域って言うのは、このセッションオブジェクト毎に管理されるんや。コンストラクタの引数で、キャッシュの初期サイズ(ページ数)や最大サイズも指定する事が出来るで。 とはいっても、動的に拡張させるとそれなりにオーバヘッドがかかるから、トランザクション内で処理するオブジェクトの総量がわかっているんやったら、はじめからそのサイズに見合うだけの初期サイズを指定しておくのも有効やね。
Session session = new Session();
or
Session session = new Session( (int)cacheInitialPages, (int)cacheMaximumPages);
// フェデレートデータベースの参照を取得getFDメソッドで、現在接続されているフェデレートデータベースの参照を取得する事が出来るんや。フェデレートデータベースの参照を使ってデータベースの検索や、生成を行ったり、データベース全体にかかわってくるような操作は、この参照を通じて操作することになるから、何はさておきゲットしておく事やな。トランザクションは開始してなくてもかめへん。
ooFDObj fd = session.getFD();
// トランザクション開始Objectivity for Javaでは、トランザクションの開始を明示的に示さなあかんねん。
session.begin();
フェデレートデータベースの下の階層にあたるデータベースは、oonewdbツールを利用するか、APIを呼び出して動的に生成する方法があんねん。今回はその動的に生成する例をあげてみてん。ちなみに"TEST_DB"っていう名前はシステムネームってゆって、この名前を使ってデータベースを特定する事ができるわけや。// フェデレートデータベースにTEST_DBというデータベースが存在すれば参照を取得
// 存在しなければ新たに生成して参照を取得 ooDBObj db; if (fd.hasDB("TEST_DB")) { db = fd.lookupDB("TEST_DB"); } else { db = fd.newDB("TEST_DB"); }
データベースと同じで、システムネーム"TEST_CONT"のコンテナを検索して、無ければ作ってんねん。データベースと違うのは、コンテナはnew 演算子でooContObjのインスタンスを生成してから、どれかのデータベースに追加するっていう流れになんねん。 addContainerメソッドの引数は、(追加するコンテナの参照、追加するコンテナのシステムネーム、ハッシュコンテナ(*6)フラグ、初期ページ数、拡張率)ってなってんねん。あっあとコンテナはツールでは作れへんから、APIを使って作るしか方法が無いで。// データベース(TEST_DB)にTEST_CONTというコンテナが存在すれば参照を取得 // 存在しなければ新たに作成してデータベースに追加し、参照を取得 ooContObj cont; if (db.hasContainer("TEST_CONT")) { cont = db.lookupContainer("TEST_CONT"); } else { cont = new ooContObj(); db.addContainer(cont, "TEST_CONT", 1, 10, 100); cont = db.lookupContainer("TEST_CONT"); }
// PersistentProductのインスタンスを生成永続化可能オブジェクトの生成やけど、特別な操作は必要あらへん。Javaのクラスのインスタンスを作るときと同じでnew演算子を使うねん。せやけど、この時点では、オブジェクトはJVM上にあるだけで、データベースには格納されてへんで。
PersistentProduct product = new PersistentProduct();
// くらすたぁ!!これですわ。これが永続化可能クラスのインスタンスをデータベースに格納させるためのメソッドですわ。clusterメソッド自体はooObjクラスがもつメソッドやから、ooObjクラスを継承しているオブジェクトに対して呼び出すことができんねんけど、とりあえずここでは、「あるコンテナのclusterメソッドを呼び出せは、引数で指定したオブジェクトは、そのコンテナの中に格納される」っていうことや。ちなみに、clusterメソッド等で、オブジェクトの格納場所を明示的に決定する事をクラスタリングいうねん。でも実はこの時点でも、まだデータベースにオブジェクトは存在してへんで。
cont.cluster(product);
// トランザクションをコミットトランザクションのコミットや。この時点で実際にデータベースにオブジェクトが書き込まれるわけや。逆にゆーたら、コミットを忘れてもうたら、それまでのどんな操作もデータベースに反映されへんねん。
session.commit();
// トランザクションをアボートコミットとは逆で、トランザクションのアボートによって、トランザクション中に行った操作を全て無効にできるねん。無効するっていうか、それまでの更新内容をデータベースに書き込まないっていう表現の方が近いかも知れへんね。
session.abort();
ま、上のソースコードの解説はこんなもんかな。データベースとの接続や、トランザクションについては、もっと細かい指定や操作ができるんやけど、一度に紹介しきれへんし、まずは最低限必要な事からということで、ぼちぼちいきましょうや。」
す「そうだね。こうして実際のソースコードを見ると、Java言語だけで、データベースを定義したり、トランザクションを制御したり、その中でデータを操作できるって事がよくわかったよ。
コンパイル方法や、データベースの内容を確認する為のツールの使い方は、この前習ったから、早速実行してみよっと・・・」(*6) ![]()
Fig.10 TestApp.javaの実行結果
ハッシュコンテナとは、名前付けの方法の1つであるスコープネームを管理するためのハッシュテーブルを持ったコンテナです。スコープネームについては、次回の連載で紹介します。
9.次回予告
今日から始めるオブジェクト指向データベース、2回目の連載はいかがだったでしょうか?
前回とはがらりと変わって、ソースコードを見ながら実装の方法について触れてみました。今回紹介させていただいた内容は、あくまで「これを知らなければ動かせない」というレベルの内容です。次回は「知らなくても何とかなるけど、知っていれば実装コストやパフォーマンスがぐっと良くなる」的な内容を紹介しようと思います。乞うご期待!
© 2002 OGIS-RI Co., Ltd. |
|