DataStoreの利用

概要: DataStoreコンポーネントは、パフォーマンスの高いデータキャッシング機能を提供し、データの永続化を容易に実現します。DataStoreをDataExpressで使用すれば、大容量データを扱うクライアントシステムのパフォーマンス向上を計ることができます。また、オフラインやモバイルシステムで、非同期データのための持続的キャッシュのためにも用いることができます。

DataStoreコンポーネントは、パフォーマンスの高いデータキャッシング機能を提供し、データの永続化を容易に実現します。DataStoreをDataExpressで使用すれば、大容量データを扱うクライアントシステムのパフォーマンス向上を計ることができます。また、オフラインやモバイルシステムで、非同期データのための持続的キャッシュのためにも用いることができます。

DataStoreコンポーネントは、JBuilder 2 Client/Server Suiteに搭載されています。

    DataStoreの基本操作

    DataStoreの作成

DataStoreは、fileNameプロパティに従ってファイルを作成します。新規にDataStoreファイルを作成するには、create()メソッドを用います。以下は、ファイルが存在しなければ作成するコードの例です。

if (!(new java.io.File(dataStore1.getFileName()).exists()))
  dataStore1.create();

既存のファイルを破棄し、常に新しいDataStoreファイルを作成したい場合は、以下のようにします。

File storeFile = new java.io.File(dataStore1.getFileName());
if (storeFile.exists())
  storeFile.delete();
dataStore1.create();

    オブジェクトの保管と復元

DataStoreコンポーネントは、任意のオブジェクトの格納庫として利用できます。オブジェクトの保管には、writeObject()メソッドを使います。このメソッドは2つの引数をとります。最初の引数は、Stringです。ここには、StoreNameつまり、オブジェクトを保管する一意の名前を付けます。2番目の引数には、保管するオブジェクトを指定します。次の例は、EmployeeオブジェクトをDataStoreに保管する例です。

Employee emp = new Employee();
try {
  dataStore1.writeObject("EMPLOYEE1", emp);
}
catch (Exception ex){
  // Error 
}

保管されたオブジェクトは、名前によって管理されます。readObject()メソッドは、StoreNameによってDataStoreに保管されたオブジェクトを取り出します。

try {
  Object obj = dataStore1.readObject("EMPLOYEE1");
  textControl1.setText(obj.toString());
}
catch (Exception ex){
  // Error
}

    ディレクトリ情報へのアクセス

DataStoreでは、ファイルシステムによって、複数のオブジェクトなどを格納、管理します。このファイルシステム、すなわちディレクトリ情報へのアクセスは、openDirectory()メソッドによって可能です。openDirectory()は、borland.jbcl.dataset.StorageDataSetオブジェクトでディレクトリ情報を返します。

borland.jbcl.dataset.StorageDataSetは、JBuilderのデータベースアクセス機能を提供するDataExpressのコンポーネントです。つまり、DataStoreのディレクトリ情報には、DataExpressのテーブルの形式でアクセスできます。

StorageDataSetを使用するには、borland.jbcl.datasetパッケージをインポートします。

import borland.jbcl.dataset.*;

openDirectory()は、以下の列を持ったデータセットを返します。

Table 1 ディレクトリ情報に含まれる列

説明

State

short

ストリームの状態を表わす定数(~_STATE)を保持

DeleteTime

long

ストリームが削除されたUTC時間

StoreName

String

ストリームの192バイトの名前

Type

short

ストリームの種別を表わす定数(~_STREAM)を保持

Id

int

ストリームの一意な識別子

Properties

String

DataSet ストリームの持続プロパティ/イベント

ModTime

long

ストリームが変更された最後のUTC時間

Length

long

ストリームの長さ(バイト数)

BlobLength

long

テーブルストリームのBLOBの長さ(バイト数)

例えば、ListControlにDataStoreのディレクトリ情報を表示するには、以下のようなコードを記述します。

gridControl1.setDataSet(dataStore1.openDirectory());

GridControlを用いれば、すべての情報を表示できます。ただし、StoreName以外の列のvisibleプロパティはデフォルトでfalseになっています。すべての情報を表示するには、以下のように、各列のvisibleプロパティに対し、borland.jbcl.util.TriStateProperty.TRUEを設定します。

try {
  StorageDataSet dir = dataStore1.openDirectory();
  for (int i = 0; i < dir.getColumnCount(); i++)
    dir.getColumn(i).setVisible(
      borland.jbcl.util.TriStateProperty.TRUE);
  gridControl1.setDataSet(dir);
}
catch (Exception ex){
  // Error
}

borland.jbcl.util.TriStatePropertyは、DEFAULT、FALSE、TRUEの3つの値を保持できるプロパティを実装するために使用されるインターフェースです。

各列の名称は、borland.datastore.DataStoreクラスで定義されています。

Table 2 DataStoreクラスで定義された列名定数

定数

DIR_ACCESS

(将来の使用のために予約)

DIR_BLOB_LENGTH

BlobLength

DIR_DEL_TIME

DelTime

DIR_ID

Id

DIR_LENGTH

Length

DIR_MOD_TIME

ModTime

DIR_PROPERTIES

Properties

DIR_STATE

State

DIR_STORE_NAME

StoreName

DIR_TYPE

Type

コードによって、ディレクトリ情報にアクセスするには、StorageDataSetの以下のようなレコード操作のメソッドを用います。

Table 3 StorageDataSetのレコード移動メソッド

メソッド

説明

first()

最初のレコードに移動します

next()

次のレコードに移動します

prior()

前のレコードに移動します

last()

最後のレコードに移動します

inBounds()

次のレコードに移動できるかどうかを返します

locate()

レコードを検索します

lookup()

条件に一致するレコードの内容を取得します

次の例は、コードによってディレクトリ情報にアクセスし、その結果をTreeControlに表示するものです。

try {
  // ディレクトリ情報を取得
  StorageDataSet dir = dataStore1.openDirectory();
  // TreeControlのルート項目を設定
  GraphLocation root = treeControl1.setRoot(dataStore1.getFileName());

  for (dir.first();dir.inBounds(); dir.next()) {
    // StoreNameを取得して項目に追加
    GraphLocation loc =
      treeControl1.addChild(root,
        dir.getString(DataStore.DIR_STORE_NAME));
    // 更新時刻とサイズを取得して子項目に追加
    treeControl1.addChild(loc,
      new java.util.Date(dir.getLong(DataStore.DIR_MOD_TIME)));
    treeControl1.addChild(loc,
      dir.getLong(DataStore.DIR_LENGTH) + "bytes");
  }
}
catch (Exception ex){
  // エラー処理
}

各列のデータは、この例で使用しているようにget~()メソッドを用います。Table 1に示したデータ型に応じて、メソッドを使い分けます。例えば、String型のStoreName項目はgetString()、short型のState項目はgetShort()になります。

String storeName = dir.getString(DataStore.DIR_STORE_NAME);
short state = dir.getShort(DataStore.DIR_STATE);

ディレクトリ情報は、リードオンリーです。このデータセットに値を設定して、直接ディレクトリ情報を変更することはできません。

DataExpressでの利用

    DataExpressでのDataStoreの利用

    DataExpress

JBuilderでは、JDBCによるデータベースアクセスをカプセル化したDataExpressコンポーネントが提供されています。このコンポーネントを用いれば、直接JDBC APIを用いた冗長なコーディングを行う必要がなくなります。しかも、DataExpressが提供するデータセットコンポーネントは、カスタマイズ可能なストレージ機能を持っており、データベースへの問い合わせの結果取得したデータセットを、クライアント環境でキャッシュすることができます。ここで、DataStoreを用いれば、データセットのキャッシュはディスクに格納され、永続的なキャッシュになります。

    DataStoreによるディスクキャッシュ

DataExpressでDataStoreを使ったディスクキャッシュを実装するのは極めて簡単です。データベースに問い合わせを行い結果セットを取得するQueryDataSetコンポーネントには、storeプロパティがあり、これを設定することで簡単にディスクキャッシュへの切り替えを実行できます。

    注意事項

DataExpressでDataStoreを使用する場合、以下の点に注意しなければなりません。

  • すべてのデータアクセスコンポーネントは、DataStoreとともにDataModuleに配置されている必要があります。特定のDataStoreを使うすべてのQueryDataSetやTableDataSetなどのStorageDataSetを、DataStore が置かれているDataModule上に置きます。これによって、キャッシュやデータアクセスコンポーネントのインスタンス化に関する、複雑な問題を回避することができます。
  • 不要になったDataStoreは、close()メソッドを使って明示的に閉じます。自動的なディスクへの書き込みに加えてclose()メソッドを実行することで、DataStoreの破棄以前に、確実にキャッシュをディスクへ保存できます。
  • storeプロパティが設定されているDataStoreが不要になったときには、明示的にクロースします。これによって、DataSetに結び付けられているDataStoreのリソースが解放され、DataSetに対してガベージコレクションが働きます。

    DataStoreを使用する手順

    コンポーネントの配置とプロパティの設定

DataExpressでDataStoreコンポーネントを使用する手順は、通常のDataExpressによるデータベースアプリケーション開発の手順とあまり変わりません。使用するコンポーネントは、Database、QueryDataSetそしてDataStoreコンポーネントです。これに加えて、ユーザーインターフェースを構成するJBCLやSwingなどのコンポーネントを使います。

最初のステップは、Databaseコンポーネントの設定です。ビジュアル設計ツールで配置したDatabaseコンポーネントのconnectionプロパティを設定します。次に、DataStoreコンポーネントを配置します。ここでは、fileNameプロパティを設定します。

そして、問い合わせを実行するQueryDataSetコンポーネントを配置します。queryプロパティで、SQLステートメントを設定します。次に、storeプロパティ、storeNameプロパティを設定します。storeプロパティを設定するには、インスペクタのドロップダウンリストを使います。ここには配置したDataStoreコンポーネントが表示されます。storeNameプロパティには、任意の名前を設定します。

ビジュアル設計ツールでの作業手順をまとめます。

  1. Databaseコンポーネントの配置
  2. connectionプロパティの設定
  3. DataStoreコンポーネントの配置
  4. fileNameプロパティの設定
  5. QueryDataSetコンポーネントの配置
  6. queryプロパティの設定
  7. storeプロパティの設定
  8. storeNameプロパティの設定

    必要なコードの記述

「DataStoreの基本操作」で解説したように、DataStoreを使用するには、DataStoreファイルを生成しなければなりません。ディスクキャッシュを一時キャッシュとして利用する場合、毎回新しいDataStoreファイルを生成し、以前のキャッシュを破棄します。以下は、フレームクラスのコンストラクタで、DataStoreファイルを削除してから再生成しています。

public Frame1() {
  try{
    jbInit();
    // DataStoreファイルが存在すれば削除
    File storeFile = new File(dataStore1.getFileName());
    if (storeFile.exists())
      storeFile.delete();
    // 新しいDataStoreファイルを生成
    dataStore1.create();
  }
  catch(Exception e){
    e.printStackTrace();
  }
}

この例では、Fileオブジェクトを利用しているので、java.ioパッケージのインポートが必要です。

import java.io.*;

持続的なキャッシュを利用する場合は、以前作成されたDataStoreファイルがあればそれを用います。コンストラクタの記述は、次のようになります。

public Frame1() {
  try{
    jbInit();
    // DataStoreファイルが存在しなければ生成
    if (!(new File(dataStore1.getFileName()).exists()))
        dataStore1.create();
  }
  catch(Exception e){
    e.printStackTrace();
  }
}

一般的には、アプリケーションを終了するときに、DataStoreをクローズします。フレームクラスのwindowClosingイベントなどでclose()メソッドを呼び出し、クローズ処理を実行します。

void this_windowClosing(WindowEvent e) {
  try {
    if (dataStore1.isOpen())
      dataStore1.close();
  }
  catch(Exception ex){
    ex.printStackTrace();
  }
}

    ユーザーインターフェースの設計

DataStoreを使用したデータベースアプリケーションでも、通常のデータベースアプリケーションと同じユーザーインターフェースの設計ができます。DataExpressでは、JBCLやSwingコンポーネントとの連携をdataSetプロパティによって行います。JBCLのコントロール、DataExpress対応のSwingであるdbSwingなどのdataSetプロパティにQueryDataSetを設定すれば、データセットとの連携が可能になります。データは、自動的にコントロールに表示され、レコードの移動や更新に応じて、表示内容が変更されます。このとき、DataStoreを使用しているかどうかは関係ありません。

    複数のデータキャッシュの連携

    高度な機能でのDataStoreの利用

DataExpressでは、マスター詳細関係や集合列など、単純なデータベースアクセス以上の機能を多数提供しています。これらの機能を使用するときに、DataStoreによるディスクキャッシュを用いることもできます。

ひとつのDataStoreファイルは、複数のストリームを格納できます。マスター詳細関係にある複数のテーブルや、集合列などの情報を格納するには、この機能を利用します。DataStoreファイルは、単純にStoreNameによって設定されるこれらのキャッシュデータを、相互に連携できるように管理します。

    マスター詳細関係にあるテーブルのディスクキャッシュ

マスター詳細関係にある2つのデータセットを、DataStoreを使ってキャッシュするには、それぞれのQueryDataSetのstoreプロパティに同じDataStoreを設定し、storeNameプロパティに異なる名前を設定します。

マスター詳細関係の定義は、詳細側のQueryDataSetのmasterLinkプロパティを設定して行います。masterLinkプロパティの設定には、以下のようなダイアログボックスが用意されています。

Fig.1 masterLinkプロパティの設定

Hide image
fig1

[マスターデータセット]項目にマスター側のQueryDataSetを設定すると、双方のデータセットで一致する列をリンク項目として表示します。異なる列名間でリンクを行うには、[使用可能なマスター列]と[使用可能な詳細列]からリンクする列を選択します。

このダイアログには、[必要になるまで詳細レコードの取得を遅らせる]というオプションがあります。このオプションは、プロパティ値を表わすMasterLinkDescriptorのfetchAsNeeded引数に相当します。このオプションをチェックしたときは、オンデマンドで詳細列を取得します。ディスクキャッシュにデータセットのコピーを保持するという目的では、このオプションは不要です。オプションのチェックをはずして、[OK]ボタンをクリックします。

以上の操作で、一度データベースからデータセットを取得すると、すべてのデータがディスクにキャッシュされ、このキャッシュを検索してマスター列に対応する詳細列を抽出表示します。

マスター詳細関係の定義方法の詳細については、「データベースアプリケーション開発者ガイド」の「マスター/詳細関係の確立」を参照して下さい。

    データの更新

    データベースへの更新

DataStoreを用いたデータベースアプリケーションの更新処理について解説する前に、DataExpressの更新処理のしくみに触れておく必要があります。DataExpressでは、QueryDataSetなどのStorageDataSetが、ローカル環境にメモリまたはディスクにキャッシュを保持します。つまり、ユーザーは、ある時点でのデータベースのスナップショットに対して操作を行っていることになります。

データベースに対してSELECT文を発行すると、その時点での結果セットが生成されます。DataExpressは、この結果セットを指定したオプション(スレッドを使ってダウンロードするか必要なデータだけをダウンロードするかなど)に従ってダウンロードします。いずれにしても、クライアントが操作できるのはスナップショットであり、サーバ上のアクティブなデータではありません。DataExpressでは、クライアントによって行われた編集結果をローカルのキャッシュに反映させます。この変更がデータベースに反映されるのは、DatabaseコンポーネントまたはQueryDataSetのsaveChanges()メソッドを呼び出したときです(Fig.2)。

Fig.2 DataExpressでのデータの流れ

Hide image
fig2

saveChanges()を実行すると、変更はデータベースに反映されますが、このとき更新が失敗する可能性があります。既に同じデータに対して、他のクライアントが更新を行っていたり、データベースへの接続が失われた場合などです。データベースへの更新処理は、単一のトランザクションとして扱われ、一連の更新処理のどこかでエラーが発生すると、すべての処理はロールバックされます。複数のデータセットに対する更新を単一のトランザクションとして扱いたい場合は、DatabaseのsaveCanges()メソッドを用います。

try {
  database1.saveChanges( new DataSet[] { queryDataSet1, queryDataSet2 });
}
catch (Exception ex) {
}

    DataStoreのキャッシュへの変更

DataStoreを用いて持続的なキャッシュを実装している場合、結果セットのスナップショットを持続的に保持しているという考え方ができます。データベースから取得した結果セットをDataStoreのディスクキャッシュに格納すると、このキャッシュは永続的に使用できますが、このデータは、データベースに問い合わせを実行したときのスナップショットです。

DataStoreによるディスクキャッシュを用いたデータベースアプリケーションで、クライアントがデータを変更するとその内容は直ちにDataStoreに格納されます。通常、この操作はメモリに対して行われますが、DataStoreはディスクに永続的なキャッシュを展開しているので、データベースに変更を反映させなくても、この結果は永続的に保持されます。もちろん、この変更結果はクライアント固有のものです。データベースサーバには、saveChanges()を実行しない限り、変更は反映されません。

    DataStoreからデータベースへの更新処理

DataStoreに貯えられた変更結果は、通常のデータベースアプリケーションと同じように、DatabaseやQueryDataSetのsaveChanges()メソッドによってデータベースサーバに反映されます。saveChanges()では、通常のデータベースアプリケーションと同じように、データベースサーバに接続され、同じデータを他のクライアントが変更しておらず、その他データベースサーバが変更を許容する場合に、更新が成功します。DataStoreによるキャッシュでは、場合によって非常に時間の経過したディスクキャッシュを用いる可能性があります。しかし、このような場合でも、データの更新は、通常のキャッシュからの更新と同じように、デフォルトの更新制御の方法で処理が実行されます。

QueryDataSetがデフォルトで実行する更新制御をカスタマイズするには、QueryResolverコンポーネントを使います。このコンポーネントを用いれば、各列データの更新、削除、挿入操作を行うときの個別の処理や、エラーに対する動作などを定義できます。

データの更新制御の詳細については、「データベースアプリケーション開発者ガイド」の「デフォルトのリゾルバロジックをカスタマイズする」を参照して下さい。

実際のシステムでのDataStoreの利用

    モバイルクライアントでのDataStoreの利用

    非同期データの格納

通信リソースの限られたモバイルクライアントでは、一度取得したデータをディスクキャッシュによって永続的に保持することで、パフォーマンスを向上させることができ、通信コストも節約できます。主として参照を目的とする非同期のデータセットは、DataStoreに格納してクライアントに配置した方が効率的です。このようなデータは、初めてアプリケーションを実行するときにダウンロードして持続的キャッシュに格納することもできますが、アプリケーションとともにDataStoreファイルとして配布してしまうこともできます。

    ファイルによるデータセットの配布

DataStoreを用いたディスクキャッシュのメリットのひとつに、キャッシュの可搬性があります。キャッシュは、DataStoreファイルとして、storeFileプロパティで設定したファイルに格納されます。このファイルは、他の環境にコピーして使用することができます。DataStoreファイルは、プラットフォームに依存しません。

    更新時刻を参照したキャッシュの更新

DataStoreファイルには、各データセットキャッシュの最終更新時刻が記録されています。この時刻と、データベースサーバに格納された非同期データの最終更新時刻を比較すれば、データを更新すべきかどうかが分かります。以下のコードは、queryDataSet1のディスクキャッシュが最後に更新された時刻を取得し、時刻tと比較して古ければ再びデータベースサーバからデータを取得する例です。

try {
  StorageDataSet dir = dataStore1.openDirectory();
  // 検索内容を格納する列オブジェクト
  DataRow row = new DataRow(dir, DataStore.DIR_STORE_NAME);
  // 検索結果を格納する列オブジェクト
  DataRow result = new DataRow(dir);
  
  // 検索内容を設定
  // queryDataSetのstoreNameプロパティの値を検索
  row.setString(0, queryDataSet1.getStoreName());
  
  // 検索を実行
  if (dir.lookup(row, result, Locate.FIRST)) {
    // 検索結果から修正時刻を取得
    short mod_t = result.getLong("ModTime");
    // 時刻 tと比較して古ければデータを再ロード
    if (mod_t < t)
      queryDataSet1.refresh();
  }
}
catch (Exception ex){
  // エラー処理
}

QueryDataSetのrefresh()メソッドは、キャッシュを更新します。データベースに接続しているときに限って、このメソッドを実行します。データベースに接続しているかどうかは、DatabaseのisOpen()メソッドを用いてチェックができます。

if (database1.isOpen())
  queryDataSet1.refresh();

    サーバサイドでのDataStoreの利用

    ローカルキャッシュとしての役割

データベースに限らず、任意のデータを格納できるDataStoreは、サーバサイドのオブジェクトの一時データ格納庫として利用できます。DataStoreの永続的なデータ保管機能を利用して、RMIやCORBAのオブジェクトが保持するデータをDataStoreに格納し、データを永続化することもできます。

DataStoreは、単一のファイルに複数のオブジェクトやファイルを格納できるので、分散化された環境でオブジェクトをレプリケーションする場合でも、付随するファイルのコピーが簡素化されます。

    マルチユーザーアクセスへの対応

DataStoreは、マルチスレッドで動作しますが、それ自身マルチユーザーには対応していません。サーバサイドでDataStoreを用いる場合、サーバオブジェクトの単一のインスタンスが特定のDataStoreファイルにアクセスする限り、マルチクライアントからのアクセスに対応できます。