Devrace FIBPlus |
InterBase/Firebirdと協調するマスター・詳細メカニズムの特色 |
この翻訳資料は原典を公開している Devrace社 と 翻訳をされた林務様、
そして、2003年9月Delphiマガジンvol.30に記事掲載していただいた PSネットワーク様、Web用にまとめられた
DipMeshSystems 三浦しゅう様のご理解・ご協力を得ましてここに掲載させていただきます。
キムラデービー木村明治 技術的な詳細に立ち入る前に、FIBPlusについてご説明したいと思います。FIBPlusはボーランド社のDelphi、C++Builder、 Kylix向けのネイティブなコンポーネントライブラリです、InterBaseとそのクローン(FirebirdとYaffil)すべてに対して、ダイ レクトにInterBase APIを通して接続し、開発者がより効果的に利用できるようにしています。具体的には、開発者がFIBPlusを利用することで、InterBaseのす べての能力を最大限利用することが可能です:トランザクションのフルコントロール、最大限の速度、InterBaseの特殊機能(例えば配列型フィール ド)、他。それに加えて、上述したようなFIBPlusコンポーネントの機能は、完全に標準のデータベース・コンポーネントと交換性を保っています。これ からそれをお見せしたいと思います。 マスター・詳細リンクの使用法は、リレーショナルデータベースを利用しているデベロッパーの間で は、最も広く利用されている手法の一つです。実際、データベースの最適化に関するリレーショナルモデルからのアプローチは、幾重ものマスター・詳細リンク を作成することを意味します。そのため、データベースの最適化後に、デベロッパーはユーザーインターフェースを出来るだけ単純にするためにも、データベー スの構造を理解して、マスター・詳細リンクを駆使しなくてはならないでしょう。 それでは、InterBaseの標準インストールファイ ルであるサンプルデータベースを例にとって、マスター・詳細リンクの制御を見てみることにしましょう。サンプルデータベース(EMPLOYEE.GDB) の二つのテーブル:DEPARTMENTとEMPLOYEEの構造を以下に示します。 CREATE TABLE DEPARTMENT ( DEPT_NO DEPTNO NOT NULL, DEPARTMENT VARCHAR (25) NOT NULL, HEAD_DEPT DEPTNO, MNGR_NO EMPNO, BUDGET BUDGET, LOCATION VARCHAR (15), PHONE_NO PHONENUMBER DEFAULT '555-1234', DEPT_NO1 CUSTNO); ALTER TABLE DEPARTMENT ADD PRIMARY KEY (DEPT_NO); CREATE TABLE EMPLOYEE ( EMP_NO EMPNO NOT NULL, FIRST_NAME FIRSTNAME NOT NULL, LAST_NAME LASTNAME NOT NULL, PHONE_EXT VARCHAR (4), HIRE_DATE DATE DEFAULT 'NOW' NOT NULL, DEPT_NO DEPTNO NOT NULL, JOB_CODE JOBCODE NOT NULL, JOB_GRADE JOBGRADE NOT NULL, JOB_COUNTRY COUNTRYNAME NOT NULL, SALARY SALARY NOT NULL, FULL_NAME COMPUTED BY (last_name || ', ' || first_name)); ALTER TABLE EMPLOYEE ADD PRIMARY KEY (EMP_NO); ALTER TABLE EMPLOYEE ADD FOREIGN KEY (DEPT_NO) REFERENCES DEPARTMENT (DEPT_NO); DEPARTMENTテーブルは明らかにマスターテーブルで、EMPLOYEEテーブルは詳細テーブルの一つとなっています。マスター・詳細リンクは以下のフィールドで結合されています: DEPARTMENT.DEPT_NO <- EMPLOYEE.DEPT_NO それぞれの部署(department)毎に、その従業員に関する情報を登録することができるわけです。 Setp 1. データの取得とTDBGridでの表示では、アプリケーション作成のプロセスを最初から追ってみましょう。このアプ リケーションでは部署と従業員に関するデータを編集できるようにしたいと思います。まず、メインコンポーネントをフォーム上に配置します、これは InterBaseへの接続を行うコンポーネント(TpFIBDatabase)です。配置後にDatabase Editorを実行します。![]() Figure 1. ![]() Figure 2. データベースに接続するためには、最低限ファイルのパスを設定し(この例ではローカルファイルへのパスになっています)、ユーザー名とパスワードを指定し ます。パラメータが正しいかどうかを「Test」ボタンで検証することができます。また、実行時にデータベース接続のパラメータを設定することも可能で す。 ![]() Figure 3. procedure TForm1.Button1Click(Sender: TObject); begin pFIBDatabase1.DBName := DBNameE.Text; pFIBDatabase1.ConnectParams.UserName := UserNameE.Text; pFIBDatabase1.ConnectParams.Password := PasswordE.Text; pFIBDatabase1.Open; end; さて次は、データベースからデータを取得してみます。このためには2つのコンポーネント(TpFIBDataSetとTpFIBTransaction) が必要となります。TpFIBDataSetは、TDataSetを継承していて標準のビジュアルコンポーネントと完全な交換性を保っています (DataSource1は実際にpFIBDataSet1に接続しています)。TpFIBTransactionはトランザクション制御のために存在し ています。 ![]() Figure 4. 次に、TPBModeプロパティを利用してトランザクションのアイソレーション・レベルを設定する必要があります、それからpFIBTransaction1をpFIBDatabase1に接続します。 ![]() Figure 5. 同様に、pFIBDataSet1コンポーネントのデータベースとトランザクション・プロパティを設定します。 ![]() ![]() Figure 6. Figure 7. さて、これでテーブルのデータを取得したり更新したりするために必要なデータベースに対するクエリーを設定することが出来るようになりました。SQLのSELECT文を利用してデータを取得するクエリーを書いてみましょう。このケースではとてもシンプルです。 SELECT * FROM DEPARTMENT 今度は、プロシージャでコードを少しだけ変更してみましょう。 procedure TForm1.Button1Click(Sender: TObject); begin pFIBDatabase1.DBName := DBNameE.Text; pFIBDatabase1.ConnectParams.UserName := UserNameE.Text; pFIBDatabase1.ConnectParams.Password := PasswordE.Text; pFIBDatabase1.Open; pFIBDataSet1.Open; end; クエリーをオープンする前に、明示的にトランザクションを開始する必要はありません、というのもpFIBDataSet1.Opetionsはデフォルト でpoStartTransactionキーを含んでいるので、コンポーネントは自分自身でトランザクションを開始するからです。アプリケーションが起動 して、データベースをオープンすると、下図のようになります。 ![]() Figure 8. 例文コードはこちらです。 Setp 2. ライブクエリーの作成‐ジェネレータを利用した主キーの設定DBGrid1で何らかの値を変更してみようとするとわ かりますが、いま設定されているのは読み取り専用クエリーです。このクエリーを編集可能(あるいはアライブ)にするためには、いくつか追加のクエリーを書 かなくてはなりません。それらはDBGrid1のデータを編集すると、pFIBDataSet1によって自動的に実行されるものです。以下のプロパティを 設定する必要があります。![]() Figure 9. FIBPlusは更新クエリーを生成及び編集するための特別な設計時エディタを含んでいます。pFIBDataSet1を右クリックして、コンテクスト・メニューから「SQLジェネレータ」を呼び出すことが出来ます。 ![]() Figure 10. 更新クエリーの生成のために、まず最初にリストから主テーブルを設定します。もしテーブルを一つしか使用していないのであれば、リストから自動的に選択さ れます。その後、「Get Table Fields」ボタンを押して、「Key Fields」リストと「Update Fields」リストを取得します。最初のリストでは、全ての更新クエリーでWHERE句に挿入されるフィールドを選択します。「Update Fields」リストでは、更新対象のフィールドを選択します。デフォルトでは、DEPARTMENTテーブルの全てのフィールドが選択されています。こ こで「Generate SQLs」ボタンを押すと、自動的に全ての更新クエリーが生成され、SQLsタブで確認することが出来ます。例えば、InsertSQLプロパティに対し て以下のようなクエリーが生成されます。 INSERT INTO DEPARTMENT( DEPT_NO, DEPARTMENT, HEAD_DEPT, MNGR_NO, BUDGET, LOCATION, PHONE_NO ) VALUES( ?DEPT_NO, ?DEPARTMENT, ?HEAD_DEPT, ?MNGR_NO, ?BUDGET, ?LOCATION, ?PHONE_NO ) RefreshSQL プロパティのクエリーに対しては注意して下さい。それは選択クエリーでもありますが、カレント・レコードだけを返すものでなくてはなりません。すべてのク エリーを作成し終わったので、ユーザーはDBGrid1で新規レコードを挿入することが出来ます、pFIBDataSet1は?DEPT_NO、? DEPARTMENT、他のパラメータの値を、ユーザーが設定したフィールドの値によって設定します。DEPT_NOフィールドには注意して下さい。この 整数フィールドはテーブルの主キーであり、ユニークな値でなければなりません。InterBaseは「ジェネレータ」と呼ばれる、このような値を得るため の特別な機能を持っていますが、その値はデータベースでユニークであることが保証されます。ジェネレータ利用上の特性として、インサート・クエリーを実行 する前にアプリケーションの中で新しいジェネレータの値を取得しておかなくてはなりません。どうしてそのようにする必要があるのでしょうか?ポイントはど の更新クエリーもその実行後に(deletingクエリーを除く)、pFIBDataSet1は自動的にRefreshSQLプロパティにセットされたク エリーに現在のパラメータ値をセットして実行します。今回の場合、こうしてセットされる値として、主キーの値が必要となります。もし事前にこの値を取得し ないで、トリガーを利用して主キーをセットした場合、RefreshSQLクエリーにDEPT_NOの値をセットすることができないので、更新されたレ コードを再読込することが出来ません。そういうわけで、レコードのどのフィールドがデータベースのトリガーで変更されてしまったとしたら、クエリーを再度 完全に開き直さない限り変更を見ることは出来ないでしょう。しかし、最初に新しいジェネレータの値を取得して、それを他のパラメータと一緒にインサートす ると、この値を使って現在のレコードをリフレッシュすることで、不必要な再オープンを行うことなく、実際のフィールドの値を知ることが出来ます。 TpFIBDataSetは、ジェネレーターを利用して自動的に主キーの値を取得してインサートする事も出来ます。このためには、AutoUpdateOptionsを設定して下さい。 ![]() Figure 11. 最初にDEPARTMENTテーブル、DEPT_NOキーフィールド、DEPT_NO_GENジェネレータの名前を設定します。WhenGetGenIDプロパティは次の3つの値をとることが出来ます。 • wbOnNewRecord‐新規レコードの準備がバッファされた直後にジェネレータの値を取得します。 • wgBeforePost‐サーバーに新規レコードを送信する直前にジェネレータの値を取得します。 • wgNever‐キー値に対してジェネレーターを使用しません。 この例では、DBGrid1でサーバーから受け取った、DEPT_NOフィールドの値を見るためにwbOnNewRecordを値として設定してみましょ う。こうしてアプリケーションを実行すると、どのレコードを編集することも出来ますし、新しいレコードをインサートすることもできます。 ![]() Figure 12. 図にあるように、DEPT_NOフィールドを自動的に取得して、22になっているのが見て取れます。BUDGETとPHONE_NOフィールドにデフォルト値が自動的に設定されていることにも注意して下さい。 このステージの例文コードはこちらです。 Setp 3. AutoCommitモード、デッドロックを回避するためのツー・トランザクション・コンテクスト動作pFIBDataSet はAutoCimmitプロパティをTrueに設定すると、変更を自動的にコミットするようになります。この場合、Postメソッドを呼んだ後で、 pFIBDataSet1は自動的にpFIBTransaction1.CommitRetainingを呼び、クエリーを閉じることなく変更を保存しま す。このアプローチは、データ変更のコンテクストにおいて、デッドロックの蓋然性を減少させ、長時間にわたる閉じられないトランザクションを可能にしま す。一方、FIBPlusは別の方法も提供していますが、これはデッドロックの蓋然性を実質的に0にまで減少させます。 TpFIBDataSetは二つのトランザクションのコンテクストで動作することができます、その場合長いトランザクションではデータは読み取り専用のコ ンテクストにあり、短いトランザクションは全ての更新クエリーが実行されるコンテクストにあります。 pFIBTransaction1 の名前をReadTransactionに変更し、もう一つコンポーネントを追加してWriteTransactionという名前にして下さい。この後 で、pFIBDataSet1のUpdateTransacionプロパティにWriteTransactionをセットします。このようにして、 pFIBDataSet1は両方のTpFIBTransactionコンポーネントに同時に接続されました。 ![]() Figure 13. これで、pFIBDataSet1のポストメソッドを呼ぶと、WriteTransactionのコミットが呼び出されるようになります。しかし、データ は別のトランザクションのコンテクストによって読み取られているという事実に注意して下さい、pFIBDataSet1をクローズすることはありません。 そういうわけで、FIBPlusを利用すると、真の自動コミットモードを手に入れることが出来ます、これによってデッドロックの可能性は減少し、実際のレ コードの値を見ることが出来ないといったことは起きなくなります。ご存じかも知れませんが、BDEで自動コミットモードを利用した場合、クエリーを再オー プンしない限り実際のデータを取得することは出来ません。 このステージの例文コードはこちらです。 Step 4. マスター・詳細リンク、特殊な接頭辞「MAS_」をパラメータの名称に使用しますここまでで、マスター・詳細リンクの作成方法を一通りみてきました。今度は、フォームに以下のコンポーネントを配置してみます。DBGrid2: TDBGrid; DataSource2: TDataSource; pFIBDataSet2: TpFIBDataSet; ReadTransaction2: TpFIBTransaction; WriteTransaction2: TpFIBTransaction; ここで、これらを同じ方法で一つ前のコンポーネントのグループに接続しましょう。次に、pFIBDataSet2にSQLのSELECT文を書いてみます。 SELECT * FROM EMPLOYEE WHERE DEPT_NO = ?DEPT_NO わかりきったことですが、やりたいことは詳細クエリーを利用して、現在の部署で働いている従業員だけを選択したいわけです。「?DEPT_NO」パラメー タの値は、DEPARTMENTテーブルのDEPT_NOフィールドから取得しなくてはなりません。それを自動的に行うために、pFIBDataSet2 のDataSourceをDataSource1にセットします。それから、さきほどpFIBDataSet1でやったのと同じように、 pFIBDataSet2の更新クエリーを作成します。 After automatic generation we need to make some changes in the got queries. That is in particular a query for InsertSQL: 自動生成されたクエリーにちょっとした変更を加える必要があります。まずはInsertSQLを見てみましょう。 INSERT INTO EMPLOYEE( EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE, DEPT_NO, JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY ) VALUES( ?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE, ?DEPT_NO, ?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY ) ここで見て取れるのは、新規に従業員を追加した場合、「?DEPT_NO」パラメータにDEPARTMENTテーブルのDEPT_NOフィールドの現在の 値をセットしなくてはならないということです。FIBPlusは接頭辞「MAS_」を付けるとこれを自動的に行うことが出来ます。 INSERT INTO EMPLOYEE( EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE, DEPT_NO, JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY ) VALUES( ?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE, ?MAS_DEPT_NO, ?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY ) これで、EMPOYEE.DEPT_NOの値はEMPOYEEテーブルのマスターであるDEPARTMENTテーブルのDEPT_NOフィールドから取得されてセットされるようになりました。同じように、UpdateSQLも変更しなくてはなりません。 UPDATE EMPLOYEE SET FIRST_NAME = ?FIRST_NAME, LAST_NAME = ?LAST_NAME, PHONE_EXT = ?PHONE_EXT, HIRE_DATE = ?HIRE_DATE, DEPT_NO = ?MAS_DEPT_NO, JOB_CODE = ?JOB_CODE, JOB_GRADE = ?JOB_GRADE, JOB_COUNTRY = ?JOB_COUNTRY, SALARY = ?SALARY WHERE EMP_NO = ?OLD_EMP_NO 次に、プロシージャのコードをちょっとだけ変更します。 procedure TForm1.Button1Click(Sender: TObject); begin pFIBDatabase1.DBName := DBNameE.Text; pFIBDatabase1.ConnectParams.UserName := UserNameE.Text; pFIBDatabase1.ConnectParams.Password := PasswordE.Text; pFIBDatabase1.Open; pFIBDataSet1.Open; pFIBDataSet2.Open; end; さて、アプリケーションを起動してみましょう。DBGrid1を上下に移動すると、DBGrid2の表示内容が自動的に変化するのがわかります。マスター・詳細リンクはちゃんと機能しています。 ![]() Figure 14. このステージの例文コードはこちらです。 Setp 5. マスター・詳細メカニズムのカスタマイズFIBPlusは、この他にもいくつかの独自の機能をマスター・詳細メカニ ズムに対して提供しています。さきほどの例で、詳細側のデータセット(pFIBDataSet2)を手動で開いたことを思い出して下さい。単純なリンクを 扱っているのなら、こんな事は簡単ですが、いくつものマスター・詳細リンクを扱ったり、長いリンク(マスター・詳細・その詳細)だったりした場合、手動で 全てのリンクを開いていくことはエラーの原因となりかねません。いつでも詳細クエリーをマスタークエリーを開いた後で開かなくてはなりません。こうした不 必要なコーディングを避けるために、TpFIBDataSetは特別なDetailOptionsプロパティを持っています。![]() Figure 15. dcForceOpen キーをセットするとマスタークエリーをオープンした後で、自動的に詳細クエリーが開かれることが完全に保証されます。そのため、さきほどのプログラムか ら、pFIBDataSet2.Openの文を削除することが出来るわけです。いずれにしろ、マスター・詳細リンクの複雑さは、全てのクエリーが正しい順 番で開かれる必要があるということなのです。 次に重要なオプションは、dcWaitEndMasterScrollキーです。ユーザー がDBGridを操作して、必要な部署を探しているところを想像してみて下さい。ユーザーの動作によってDBGrid1が変化するたびに、 pFIBDataSet2は自動的にクエリーを開き直すことになります。明らかに、マスターテーブルの操作によって、必要以上に複雑なクエリーが引き起こ されてしまいます、これはネットワークのトラフィックを大幅に増加させてしまうでしょう。実際に必要なことは、ユーザーが最終的にDBGrid1で部署を 見つけた時に、詳細クエリーを一度だけ開き直すことです。FIBPlusはこうした冗長なクエリーを回避してくれます。 dcWaitEndMasterScrollキーをDetailConditionsプロパティにセットすると、詳細クエリーは一定の間隔をおいてから開 き直されるようになります(この間隔の値はWaitEndMasterScrollの値を設定することで指定出来ます)。 そういうわけ で、ユーザーが単純にDBGrid1を操作する場合、現在のレコードの変化は詳細クエリーのタイマーをアクティブにします。この間隔の間に、ユーザーが別 のレコードへ移動した場合、タイマーはリスタートされることになります。これはユーザーが必要なレコード上で操作を完了するまで継続して発生します。詳細 クエリーはこの間隔の後でのみ開き直されます、ということはpFIBDataSet2は一連のクエリーの代わりにたった一回だけのクエリーを実行するにす ぎないわけです。 さてこれで、FIBPlusのマスター・詳細リンクがどのように機能するかわかりました。また、どのようにデータベー スに接続し、選択クエリーを実行したり、更新クエリーを作成したり、ツー・トランザクション・コンテクストでデータがどう扱われるか、それから、マスター 側と詳細側のデータセットをどうリンクするかがわかりました。解説したやり方はとても簡単なものですが、この例を通して学習されることをお勧めいたしま す。 |