[目次] [前の項目] [次の項目]

RowSet

10.1    RowSet の概要

RowSet オブジェクトは、表形式データを格納するためのコンテナであり、特定のデータソースから取得された 0 個以上の行をカプセル化します。RowSet インタフェースの基本実装では、JDBC データソースから行を取得するようになっていますが、行セットをカスタマイズすれば、スプレッドシートやフラットファイルといった表形式のデータソースからデータを取得することも可能です。RowSet オブジェクトは、ResultSet インタフェースを拡張しています。したがって、RowSet オブジェクトは、スクロール可能や更新可能にできるほか、ResultSet オブジェクトにできることのすべてを実行できます。 この初めのセクションでは、RowSet オブジェクトの機能について簡単に説明しますが、それらの詳細については、以降の各セクションで説明します。

RowSet オブジェクトと ResultSet オブジェクトの相違点としては、前者が JavaBeansTM コンポーネントであることが挙げられます。したがって、RowSet オブジェクトは、一連の JavaBeans プロパティを備え、JavaBeans のイベントモデルに従います。RowSet オブジェクトは、それらのプロパティを使用することで、データベース接続の確立とクエリーの実行を独自に行い、取得したデータを自身に格納できます。行セットは、「切断された」状態にすることが可能です。つまり、行セットを使用している間、常にデータソースへの接続を維持しなくても、行セットが機能するようにできます。さらに、行セットは直列化できるため、行セットをネットワーク経由で特定のリモートオブジェクトに送信することが可能です。

JDBC API は一般に、2 つのカテゴリに分けられます。RowSet 部分とドライバ部分の 2 つです。RowSet とそれをサポートするインタフェースは、JDBC API のその他の部分を用いて実装されるように設計されています。言い換えると、RowSet インタフェースを実装するクラスは、JDBC ドライバ上で動作するソフトウェア層であると言えます。RowSet オブジェクトはほかの JDBC オブジェクトと違って、ドライバが存在せず、データソースに接続されていない状態での動作を可能にするための機構を備えています。

J2SE 5.0 では、3 番目のカテゴリが導入されました。RowSet API とドライバに加え、RowSet インタフェースの標準実装が 5 つ用意されています。これらの実装は、基本 RowSet インタフェースを拡張した一連のインタフェースを提供するとともに、それらの各インタフェースのリファレンス実装を提供します。必ずしもこれらの実装を使用する必要はありませんが、開発者はこれらの実装を使用することで、イベント処理、カーソル操作などを行う際に、JDBC API に確実に準拠することができます。標準実装の詳細については、「標準実装」を参照してください。

RowSet インタフェースにはすべての行セットに共通する一連の基本メソッドが含まれていますが、ここではそれらのメソッドについて説明します。すべての RowSet オブジェクトは JavaBeans コンポーネントです。したがって、RowSet インタフェースには、イベントリスナーの追加/削除メソッドと、すべてのプロパティに対する取得/設定メソッドが、含まれています。それらのプロパティの多くは、接続の設定、コマンドの実行のいずれかをサポートするためのものです。行セットは、クエリーを実行して結果セットを生成し、それを自身のデータとして使用する際に、データソースとの接続を使用します。また、行セットは、変更されたデータをデータソースに書き戻す際にも接続を使用します。さらに RowSet インタフェースには、RowSet オブジェクトのコマンド文字列に入力パラメータが含まれていた場合にそれらの値を設定するためのメソッドも、データ型ごとに 1 つずつ用意されています。JDBC RowSet の実装仕様では、RowSet インタフェースに定義されているこれらの基本メソッドの実装が、BaseRowSet 抽象クラスとして提供されています (このクラスについては後述します)。

その他の 5 つのインタフェースと 1 つのクラスは、システム内部で RowSet インタフェースと連携動作します。クラス RowSetEvent とインタフェース RowSetListener は、JavaBeans のイベントモデルをサポートします。RowSet オブジェクトはカーソル移動時またはデータ変更時に、RowSetListener の発生したイベントに対応するメソッドを呼び出し、自身をイベント発生元として指定した RowSetEvent オブジェクトを渡します。実装内では、その他の RowSet イベントの拡張を、必要に応じて自由に記述できます。

あるコンポーネントが特定の RowSet オブジェクト内で発生したイベントの通知を受け取るようにするには、そのコンポーネントに RowSetListener インタフェースを実装したあと、そのコンポーネントを目的の RowSet オブジェクトに登録します。そのようなコンポーネント (「リスナー」と呼ばれる) は通常、RowSet オブジェクトのデータを表示する、表や棒グラフなどの GUI (グラフィカルユーザインタフェース) コンポーネントです。行セット内でイベントが発生するたびに、リスナーは通知を受け取ります。したがって、リスナーと行セットとの間で、カーソル位置やデータの一貫性を維持できます。

インタフェース RowSetInternalRowSetReaderRowSetWriter は、行セットのリーダ/ライター機構をサポートします。「リーダ」とは、RowSetReader インタフェースを実装したクラスのインスタンスのことです。リーダは、データを読み込み、それを行セットに挿入します。「ライター」とは、RowSetWriter インタフェースを実装したクラスのインスタンスのことです。ライターは、行セット内の変更されたデータを、その取得先であるデータソースに書き戻します。

RowSetInternal インタフェースが提供する追加のメソッドを使えば、リーダまたはライターは行セットの内部状態を操作できます。たとえば、行セットでは元の値を追跡できるようになっていますが、ライターは RowSetInternal のメソッドを使うことで、データソース内の対応するデータがほかのユーザによって変更されたかどうかをチェックすることができます。さらに、RowSetInternal のメソッドを使えば、行セットのコマンド文字列用の入力パラメータや行セットに渡された接続が存在している場合、それらの情報を取得することもできます。最後に、リーダは RowSetInternal のメソッドを使うことで、その行セットに挿入する行を記述した新しい RowSetMetaData オブジェクトを、その行セットに設定できます。このインタフェースには RowSetInternal という名前が付けられていますが、この命名は適切です。そのメソッドは、内部的に使用されるのみで、アプリケーションから直接呼び出されることはないからです。

行セットは、「接続された行セット」、「切断された行セット」のいずれかです。接続された RowSet オブジェクトは、使用されている限り、常にデータソースへの接続を維持します。これに対し、切断された行セットは、データソースとの間でデータの読み書きを行う間だけ、データソースに接続します。接続が切断されている間、行セットは、JDBC ドライバや JDBC API の実装全体を必要としません。そうした行セットは非常に軽量であり、一連のデータを thin クライアントに送信する場合のコンテナとして理想的です。クライアントは必要であれば、データを更新し、その行セットをアプリケーションサーバに送り返すことができます。サーバ上の切断された RowSet オブジェクトは、自身のライターを使ってデータソースへの接続を確立し、データを書き戻します。その具体的な実行方法は、ライターがどのように実装されているかによります。ライターは通常、接続を確立してデータを読み込む処理を、JDBC ドライバに委譲します。

10.1.1     RowSet のイベントモデル

RowSet のイベントモデルを使えば、Java オブジェクトまたはコンポーネントは、特定の RowSet オブジェクトによって生成されたイベントに関する通知を受け取れます。その通知メカニズムの設定には、通知を受け取るコンポーネントと RowSet オブジェクト自身の両方が関係します。まず、イベント通知を受け取る各コンポーネントは、RowSetListener インタフェースを実装する必要があります。続いて、各コンポーネントを RowSet オブジェクトに登録する必要があります。それには、各コンポーネントをそのオブジェクトのイベント通知コンポーネントリストに追加します。この時点で、それらの各コンポーネントはリスナーになります。リスナーとは、特定の RowSet オブジェクトに登録された、RowSetListener のメソッドを実装したクラスのインスタンスのことです。

RowSet オブジェクト内で発生する可能性のあるイベントは、3 種類あります。「カーソルが移動された」、「特定の行が変更された (挿入、削除、更新のいずれか)」、「内容全体が変更された」の 3 つです。これらのイベントにそれぞれ対応するのが、RowSetListener のメソッド cursorMovedrowChangedrowSetChanged です。特定のイベントが発生すると、行セットは、自身をイベント発生元として指定した RowSetEvent オブジェクトを生成します。各リスナー上の対応する RowSetListener のメソッドが呼び出されますが、その際、RowSetEvent オブジェクトがそれらのメソッドに渡されます。こうして、行セットのすべてのリスナーにイベントが通知されます。

たとえば、円グラフのコンポーネント pieChartRowSet オブジェクト rset のデータを表示させたい場合、RowSetListener のメソッド cursorMovedrowChanged、および rowSetChanged を、pieChart 内に実装する必要があります。これらのメソッドの実装によって、rset でイベントが発生した際の、pieChart の実行内容が決まります。これらのメソッドの実装が完了すると、pieChartrset に登録できます。pieChart は、rset にリスナーとして追加されると、rset でのイベント発生時に通知を受け取りますが、それは、RowSetEvent オブジェクトをパラメータとして指定して対応するメソッドを呼び出すことで実現されます。その後、リスナー pieChart は、自身を更新して rset の現在のデータとカーソル位置を反映できます。pieChart に反映する必要のない rset イベントが存在する場合、そのイベントに対応する RowSetListener のメソッドを、何も処理しないように実装します。たとえば、rset の現在のカーソル位置を pieChart で表示する必要がない場合、メソッド cursorMoved 内で何も処理しないようにします。

ある特定の RowSet オブジェクトのリスナーとして指定可能なコンポーネントの数に制限はありません。たとえば、棒グラフのコンポーネント barGraph にも rset のデータを表示させたい場合、そのコンポーネントをリスナーにすることができます。それには、RowSetListener のメソッドを実装してから、そのコンポーネントを rset に登録します。次のコード行では、2 つのコンポーネント pieChartbarGraph を、rset のリスナーとして登録しています。

    rset.addRowSetListener(pieChart);
    rset.addRowSetListener(barGraph);
リスナーの削除は、メソッド RowSet.removeListener を使って似たような方法で行えます。

    rset.removeRowSetListener(pieChart);
    rset.removeRowSetListener(barGraph);
行セットのリスナーを設定するコードは何らかのツールによって生成されることが多いため、アプリケーションプログラマが行う必要があるのは、行セットを指定することと、その行セットでイベントが発生した際の通知を受け取るコンポーネントを指定することだけです。リスナーの設定が完了すると、イベント処理が実行されますが、それは主にシステム内部で実行されます。たとえば、あるアプリケーションが rset 内の特定の行を更新した場合、rset は内部的に、RowSetEvent オブジェクトを作成し、pieChartbarGraph に実装されている rowChanged メソッドに、そのオブジェクトを渡します。リスナーはイベントの発生元を知ることができます。なぜなら、rowChanged に渡される RowSetEvent オブジェクトは、rset (イベントの発生元である RowSet オブジェクト) で初期化されているからです。コンポーネント pieChartbarGraph は、それぞれ独自の RowSetListener.rowChanged メソッド実装に従って、行の表示を更新します。

次のコードでは、pieChartbarGraph を、RowSet オブジェクト rset のリスナーとして登録しています。メソッド execute の呼び出しによって rset に新しいデータが格納されると、イベント通知が内部的に実行されます (メソッド execute の詳細については、「コマンドの実行」を参照)。イベント通知処理の最初のステップとして、RowSetEvent オブジェクト rsetEvent が生成され、rset で初期化されます。続いて、pieChartbarGraph のバージョンのメソッド rowSetChanged が、引数に rsetEvent を指定して呼び出されます。これによって、リスナー pieChartbarGraph は、rset 内のすべてのデータが変更されたことを知り、それぞれ独自の rowSetChanged メソッド実装を実行します。

rset.addRowSetListener(pieChart);
rset.addRowSetListener(barGraph);
. . .
rset.execute();
  
// The following methods will be invoked behind the scenes:
RowSetEvent rsetEvent = new RowSetEvent(this);
pieChart.rowSetChanged(rsetEvent);
barGraph.rowSetChanged(rsetEvent);

10.1.2     RowSet のプロパティ

RowSet インタフェースには一連の JavaBeans プロパティが含まれているため、データソースに接続して一連の行を取得するように RowSet インスタンスを設定することが可能です。実装にもよりますが、すべてのプロパティが必要であるとは限りません。たとえば、接続の確立時に必要なのは、URL、データソース名のいずれかであるため、一方のプロパティが設定されていれば、他方は省略可能です。両方が設定されていた場合、後から設定されたほうが使用されます。コマンドをサポートしない非 SQL データソース (スプレッドシートなど) から行セットのデータを取得する場合、command プロパティを設定する必要はありません。デフォルト値が設定されているプロパティの場合、デフォルト値で問題なければ設定を省略できます。たとえば、エスケープ処理はデフォルトでオンになっているため、オフにしたい場合を除けば、アプリケーション内でエスケープ処理を設定する必要はありません (エスケープ処理については、「Statement 内の SQL エスケープ構文」を参照)。

以下に示すのは、RowSet オブジェクトのプロパティの取得/設定用として、RowSet インタフェース内に定義されている取得/設定メソッドの一覧です。ただし、メソッド getConcurrencygetType の 2 つは例外です。これらは、RowSet インタフェース内に定義されたメソッドではなく、ResultSet インタフェースから継承されたメソッドです。

getCommand setCommand
ResultSet.getConcurrencysetConcurrency
getDataSourceNamesetDataSourceName
getEscapeProcessingsetEscapeProcessing
getMaxFieldSizesetMaxFieldSize
getMaxRowssetMaxRows
getPasswordsetPassword
getQueryTimeoutsetQueryTimeout
getTransactionIsolationsetTransactionIsolation
ResultSet.getTypesetType
getTypeMapsetTypeMap
getUrlsetUrl
getUsernamesetUsername

これに加え、JDBC RowSet の標準実装には、それらの実装に固有のプロパティが数多く含まれています。

次のコードでは、DataSource オブジェクトを使ってデータソースとの接続を確立する際に通常必要とされるプロパティを設定しています (rsetRowSet オブジェクト)。

    rset.setDataSourceName("jdbc/logicalDataSourceName");
    rset.setUsername("cervantes");
    rset.setPassword("secret");
jdbc/logicalDataSourceName は、JNDI (Java Naming and Directory Interface) ネームサービスに登録されている名前です。アプリケーション内でネームサービスに論理名を指定すると、その論理名にバインドされた DataSource オブジェクトが、ネームサービスから返されます。DataSource オブジェクトと JNDI API の使用方法については、「JNDI の使用」を参照してください。接続情報をハードコードする代わりに DataSource オブジェクトを使用すれば、コードの移植性が高まり、コードの保守も容易になります。たとえば、データソースのホストマシンやポート番号が変更された場合、更新する必要があるのは、JNDI ネームサービスに入力した DataSource オブジェクトのプロパティだけであり、そのデータソースへの接続を取得する各アプリケーションを更新する必要はありません。

RowSet オブジェクトには、コマンドの実行に影響を与えるプロパティの設定メソッドも含まれています。そうしたメソッドの使用例として、次のコードでは、文を実行する際のドライバの最大待機時間として 20 秒を設定し、rset に格納できる最大行数として 1024 を設定して、rset がコミット済みトランザクションのデータのみを読み込むように設定しています。

    rset.setQueryTimeout(20);
    rset.setMaxRows(1024);
    rset.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

また、次のコード行に示すように、RowSet オブジェクトのタイプと並行処理を設定することも可能です。

    rset.setType(ResultSet.TYPE_SCROLL_INSENSITIVE);
    rset.setConcurrency(ResultSet.CONCUR_UPDATABLE);
1 行目では、スクロール可能であるが、接続が開いている間に発生した更新は反映しないように、rset を設定しています。データソースとの接続を継続的に維持する RowSet オブジェクトには TYPE_SCROLL_SENSITIVE を指定できますが、そうでない RowSet オブジェクトを、ほかのオブジェクトやトランザクションによる変更を反映するように設定することはできません。コードの 2 行目では、rset を更新可能として設定しています。したがって、rset 内のデータを変更できます。

スクロール可能な結果セットをサポートしないドライバを使用する場合でも、RowSet オブジェクトをスクロール可能にすることができます。実際、スクロール可能な結果セットを実現する手段として、通常の結果セットの代わりに行セットを使用することがよくあります。詳細については、「RowSet オブジェクト内の移動」を参照してください。

アプリケーションは、RowSet インスタンスにデータを格納するために、RowSet オブジェクトのコマンド文字列を実行します。この文字列は、実行時に結果セットを生成するクエリーである必要があります。そうして生成された結果セットから、RowSet オブジェクトのデータが取得されます。メソッド setCommand は、指定された String オブジェクトを、メソッド execute の呼び出し時に実行されるコマンドとして設定します。たとえば、次のコード行では、テーブル EMPLOYEES に含まれるすべての行の名前と給与額を選択するクエリーを、コマンド文字列として設定しています。

    rset.setCommand("SELECT NAME, SALARY FROM EMPLOYEES");
上記のコマンド文字を設定してから rsetexecute メソッドを呼び出すと、そのクエリーによって生成される結果セットとまったく同じデータ (テーブル EMPLOYEES 内の各行に対して 1 行ずつ。各行には名前と給与額が含まれる) が、rset 内に格納されます。

10.1.3     コマンド文字列のパラメータの設定

1 つ前のセクションでは、パラメータ用プレースホルダを含まないコマンドの使用例を紹介しましたが、command プロパティには、入力パラメータを取るクエリーを設定することもできます。これを可能にするために、コマンド文字列は内部的に PreparedSatement オブジェクトとして表現されています。ただし、このパラメータは出力パラメータではなく入力パラメータである必要があります。

新しく設定された RowSet オブジェクトにはデータが格納されていないため、メソッド executepopulate のいずれかを呼び出してデータを生成する必要があります。メソッド execute を使用するには、必要なプロパティを設定するとともに、すべてのプレースホルダパラメータに値を設定しておく必要があります。パラメータ値の設定は実行時に行えます。したがって、たとえば、アプリケーション実行時に対話形式でユーザから入力を受け取ることも可能です。

RowSet インタフェースは PreparedStatement インタフェースと同じく、入力パラメータの値を設定するための設定メソッドを備えています。各種データ型 (SQL99 データ型も含む) ごとに設定メソッドが 1 つずつ用意されています。次のコードでは、コマンド文字列を設定したあと、その 2 つの入力パラメータに値を設定しています。列 DEPT に格納される値の型が VARCHAR である場合、メソッド setString を使って両パラメータに値を設定します。というのも、これが VARCHAR 値を設定するためのメソッドであるからです。

    rset.setCommand(
      "SELECT NAME, SALARY FROM EMPLOYEES WHERE DEPT = ?OR DEPT = ?");
    rset.setString(1, "SALES");
    rset.setString(2, "MARKETING");
このコマンドが実行されると、販売部門とマーケティング部門に属する従業員の名前と給与額が、rset に格納されます。

RowSet オブジェクトのコマンド文字列に含まれるすべてのパラメータに値を設定しないと、そのコマンドを実行できません。切断された行セットでは、すでに設定されたパラメータが、リーダの readData メソッドによって使用されます。このメソッドは、メソッド execute によって内部的に呼び出されます。これらのパラメータ値は、行セットの内部のハッシュテーブルに格納されます。readData メソッドは、それらの値を取得するために、その行セットの RowSetInternal.getParams メソッドを呼び出します。

10.1.4     RowSet オブジェクト内の移動

javax.sql.RowSet インタフェースは java.sql.ResultSet インタフェースを拡張しています。したがって、スクロール可能な RowSet オブジェクトとスクロール可能な ResultSet オブジェクトとの間で、カーソルの移動方法に関する違いはまったくありません。RowSet オブジェクトは、ResultSet のすべてのメソッドを継承しており、実質的に、「JavaBeans コンポーネントとして動作するための機能が追加された結果セット」であると言えます。RowSet のインスタンスを使用するコンポーネントのほとんどは、それらのインスタンスを ResultSet オブジェクトとして扱うことになります。

RowSet オブジェクトと ResultSet オブジェクトのカーソル移動用メソッドは、ユーザの視点からは同一に見えますが、実装方法の点では異なっています。RowSet オブジェクトでは、カーソルが移動するたびに、そのイベントを登録されたリスナーに通知する必要があります。その結果、RowSet オブジェクトのカーソル移動用メソッドは、内部のイベント通知処理を起動するように実装されています。たとえば、メソッド next が呼び出された場合、その実装は、RowSetEvent オブジェクトを作成し、それをパラメータとして指定して各リスナーの cursorMoved メソッドを呼び出します。リスナーは、RowSetEvent オブジェクトに基づいてカーソル移動が発生した RowSet オブジェクトを特定したあと、メソッド cursorMoved の実装を呼び出します。実装内では、何も処理しなくてもかまいませんし、たとえば、メソッド ResultSet.getRow を呼び出してカーソルの現在位置を取得し、その行に対するリスナーの表示内容を更新するようにしてもかまいません。

RowSetResultSet のカーソル移動が同じであることを示すために、次のコードでは、RowSet オブジェクト rset に対して順方向のループ処理を行い、各行から取得した 2 つの値を出力しています。

    rset.beforeFirst();
    while (rset.next()) {
      System.out.println(rset.getString(1) + " " + rset.getFloat(2));
    }
その他のカーソル移動も、ResultSet インタフェースの場合と同じです。

10.1.5     コマンドの実行

RowSet インタフェースに含まれるメソッド execute は、RowSet オブジェクトにデータを格納する場合に呼び出されます。このメソッドの実装には多くのバリエーションがあり、サブタイプではデータ設定用の追加メソッドが定義されている可能性があります。execute メソッドは、行セットのプロパティを使用しますが、必要なプロパティが設定されていなかった場合、SQLException をスローします。標準プロパティが定義されていますが、RowSet の実装によっては、ほかのプロパティも定義されている可能性があります。したがって、アプリケーション開発者は、使用する実装のドキュメントによく目を通してください。

切断された行セットでは、リーダ (RowSetReader インタフェースを実装したオブジェクト) とライター (RowSetWriter インタフェースを実装したオブジェクト) が必要になります。JDBC RowSet 実装では、javax.sql.rowset.spi パッケージの SyncProvider クラス内に、リーダとライターがカプセル化されています。SPI (Service Provider Interface) と呼ばれるこのパッケージについては、後述します。SPI を使えば、リーダとライターの実装および配置が、より簡単に行えます。2 つのリファレンス実装 RIOptimisticProviderRIXmlProvider は、すぐに使える状態になっています。

また、切断された行セットは、その内部状態にリーダとライターからアクセスできるように、RowSetInternal インタフェースを実装している必要があります。リーダ/ライターフレームワークが使用できる場合、行セットの execute メソッドは、そのリーダコンポーネントとライターコンポーネントにタスクを委譲できます。

RowSet の標準実装が追加されたため、開発者によるリーダ/ライター機構の実装作業が、大幅に簡略化されるはずです。これらの標準実装にすでに含まれているリーダ/ライター機構をユーザの実装内に組み込むだけで、それらの機構を活用できるようになります。

典型的な実装では、切断された行セットの execute メソッドは、新しいデータを行セットに読み込む処理を実行するために、リーダの readData メソッドを呼び出します。readData メソッドは通常、行セットの現在の内容をクリアしたあと、必要なプロパティを取得し、データソースとの接続を確立します。設定すべきパラメータが存在する場合、readData は、行セットからそれらを取得し、行セットのコマンド文字列内に設定します。続いて、readData メソッドは、そのコマンド文字列を実行して結果セットを生成します。最後に、readData は、結果セットのデータを行セットに設定するとともに、そのデータを元の値として設定します。

リーダの readData メソッドは、行セットのメタデータを設定するように実装することも可能です。その実装方法としてはさまざまなものが考えられますが、readData メソッド内で RowSetMetaData オブジェクトを作成し、そのオブジェクトにデータソース内の読み込む列に関する情報を設定するのも、その 1 つです。続いて、readData メソッドは、その新しい RowSetMetaData オブジェクトを行セットに関連付けます。すると、行セットは、読み込まれるデータの形式を、RowSetMetaData オブジェクトに基づいて判断できるようになります。

ある行セットのコマンド文字列が実行された場合、その行セットのすべてのリスナーにその旨を通知することで、それらのリスナーが適切な処理を実行できるようにする必要があります。execute メソッドは、各リスナーの rowSetChanged メソッドを呼び出しますが、その際、各 rowSetChanged には新しく作成された RowSetEvent オブジェクトが渡されます。このオブジェクトには、イベント発生元の RowSet オブジェクトが指定されています。リスナーへの通知は、メソッド execute 内で実行されるその他のほとんどの処理と同じく、行セットを使用するアプリケーションプログラマに意識させないように内部的に実行されます。

execute メソッドのもう 1 つの役割は、行セット内に保持される「元の」値を設定することです。これらは最後の更新が実行される直前の値であり、メソッド RowSetInternal.getOriginal から返されます。RowSet オブジェクトの元の値は、データソースから取得した値と同一である場合がありますが、必ずしもそうとは限りません。最初に RowSet オブジェクトのデータとデータソースとの同期を取った時点では、元の値とデータソースから取得した値は同一になります。ところが、RowSet オブジェクトのいくつかの値を 2 回変更した場合、元の値は、データソースの値ではなく、1 回目の変更後に存在していた値になります。RowSet オブジェクトの元の値を追跡すれば、データソース内の対応する値が変更されたかどうかをチェックすることができます。

SyncProvider オブジェクトが競合の有無をチェックするように実装されていた場合、元の値とデータソース内の値が比較されます。行セットの元の値 (変更前の値) と基となるデータソースの値が同一でない場合、RowSet オブジェクトの変更値をデータソースに書き込んだ時点で競合が発生することになります。一方、値が同一である場合には、競合は発生しません。競合発生時に SyncProvider オブジェクトが新しい行セット値をデータソースに書き込むかどうかは、使用される同期モデルによります。

SyncProvider 実装が新しい値を基となるデータソースに書き込まない場合、execute メソッドは、元の値を行セット内の現在の値にリセットします。次に execute が呼び出され、行セット内のすべての値が変更された時点で、SyncProvider オブジェクトは、それらの値を取得して基となるデータソース内の値と比較し、競合の有無をチェックすることができます。

以上をまとめると、アプリケーション内で execute メソッドが呼び出されると、システム内部で多くの処理が実行されます。切断された行セットの場合、その内容が新しいデータに書き換えられ、そのデータを使用しているすべてのリスナーにイベントが通知され、メタデータが更新され、元の値に現在のデータ値が設定されます。接続された行セットの場合、execute メソッドは通常、行セットに新しいデータを設定し、リスナーにイベントを通知するだけです。接続された RowSet オブジェクトに対する変更はデータソースにも反映されるため、競合が発生する可能性はありません。

10.1.6     RowSet オブジェクトのメタデータの使用

RowSet オブジェクトは、格納するデータの列に関する一連のメタデータを保持します。RowSetResultSet から派生したインタフェースであるため、ResultSetMetaData のメソッドを使えば、ResultSet オブジェクトのメタデータを取得するのと同じ要領で、RowSet オブジェクトのメタデータを取得できます。たとえば、次のコードでは、RowSet オブジェクト rset に対する RowSetMetaData オブジェクトを作成し、rset に格納されている列の個数をチェックしています。ここで、メソッド getMetaDataResultSet のメソッドであり、その戻り値は ResultSetMetaData オブジェクトである点に注意してください。したがって、その戻り値は、RowSetMetaData オブジェクトにキャストしてからでないと、rsetmd に代入できません。

    RowSetMetaData rsetmd = (RowSetMetaData)rset.getMetaData();
    int columnCount = rsetmd.getColumnCount();
変数 rsetmd には、RowSet rset 内の列に関する情報が含まれています。rsetmd 上で任意の RowSetMetaData メソッドを呼び出すことで、rsetmd 内に格納された情報を取得できます。

インタフェース RowSetMetaData には、ResultSetMetaData に定義されている取得メソッドに対応する設定メソッドが定義されています (ただし、列のクラス名を設定するためのメソッドと、列が「読み取り専用」、「おそらく書き込み可能」、「間違いなく書き込み可能」のどちらであるかを設定するためのメソッドは存在しない)。RowSetMetaData の設定メソッドがリーダによって呼び出されるのは、リーダが、新しいデータを行セット内に読み込み、RowSet オブジェクトの列を記述する新しい RowSetMetaData オブジェクトを作成したあとです。次のコードは、リーダが内部的にどのような処理を実行するかを示したものです。リーダは、RowSet オブジェクト rowset に対する新しい RowSetMetaData オブジェクト rowsetmd を作成し、それに列の情報を設定し、最後に RowSetInternal のメソッド setMetaData を呼び出して rowsetmdrowset のメタデータとして設定しています。

    rowset.execute();
    // ... as part of its implementation, execute calls readData
    reader.readData((RowSetInternal)this);
    
    // ... as part of the implementation of readData, the reader for
    // the rowset would do something like the following to update the
    // metadata for the rowset
    RowSetMetaData rowsetmd = new ...; // create an instance of a class
    // that implements RowSetMetaData
    rowsetmd.setColumnCount(3);
    rowsetmd.setColumnType(1, Types.INTEGER);
    rowsetmd.setColumnType(2, Types.VARCHAR);
    rowsetmd.setColumnType(3, Types.BLOB);
    // ... set other column information
    rowset.setMetaData(rowsetmd);

10.2    標準実装

J2SE 5.0 のリリースでは、Java プラットフォームに RowSet インタフェースの 5 つの標準実装が含まれています。これらの実装は、独自の実装の開発を目指すユーザを支援する目的で提供されています。RowSet インタフェースは、さまざまな目的のためにさまざまな方法で実装可能です。また、そうした実装を誰が行ってもかまいません。ただし、RowSet 実装の多くがドライバベンダーによって開発され、それらの実装が JDBC 製品の一部として提供されることが予想されます。実装者は、リファレンス実装をそのまま使用してもかまいませんし、それらを基に、あるいはまったくゼロから、独自の実装を作成してもかまいません。

標準実装は 2 つの部分、すなわちインタフェースとリファレンス実装とで構成されています。インタフェースは javax.sql.rowset パッケージに、実装は com.sun.rowset パッケージに、それぞれ収められています。これらは、Java Community Process の JSR 114 を通じ、データベース分野の専門家の意見を基に開発されました。その目的は、行セットの主要機能を標準化し、開発者がそれらの標準機能を独自の実装内で活用できるようにすることでした。RowSet の標準実装を、次に列挙します。

10.2.1     実装の基本

RowSet のすべての標準実装は、抽象クラス BaseRowSet を拡張するとともに、特定のインタフェース (JdbcRowSetCachedRowSetWebRowSetFilteredRowSetJoinRowSet のいずれか) を実装しています。BaseRowSet クラスは、すべての RowSet オブジェクトに共通する機能の基本実装を提供しますが、このクラスを使用するかどうかは、開発者に任されています。BaseRowSet クラスには次のものが含まれています。

前述したように、RowSet オブジェクトは、接続された行セット、切断された行セットのいずれかです。RowSet 実装は次のように分類できます。

CachedRowSet インタフェースには、切断された RowSet オブジェクトで必要となるメソッドが含まれています。標準実装の切断された RowSet は、BaseRowSet クラスを拡張するとともに、CachedRowSet インタフェースを実装しています。これに加え、JoinRowSet 実装は JoinRowSet インタフェースを実装し、FilteredRowSet 実装は FilteredRowSet インタフェースを実装し、WebRowSet 実装は WebRowSet インタフェースを実装しています。

10.2.2     JdbcRowSet 実装の概要

JdbcRowSet オブジェクト (JdbcRowSet インタフェースの標準実装のインスタンス) は、ほかのすべての行セットと同じく、一連の行を格納するためのコンテナです。これらの行のソースは常に、ResultSet オブジェクトです。というのも、JdbcRowSet オブジェクトは接続された RowSet オブジェクトであるからです。言い換えると、JdbcRowSet オブジェクトは、JDBC ドライバ経由で DBMS との接続を常に維持します。ただし、その他の実装では、リーダ/ライター機構が正しく実装されていれば、フラットファイルやスプレッドシートなど、任意の表形式データをデータのソースとして使用することができます。

JdbcRowSet オブジェクトにはさまざまな用途があります。おそらく最も一般的な用途は、ResultSet オブジェクトをスクロール可能にすることです。これにより、スクロールをサポートしていない従来型ドライバの活用範囲が広がります。JdbcRowSet オブジェクト内の行データ (および任意の RowSet オブジェクト内の行データ) は、それらの行セットのコマンドを実行した結果として生成される ResultSet オブジェクト内の行データにほかなりません。したがって、ResultSet オブジェクト自身がスクロール可能でなかったとしても、対応する行セットをスクロール可能にすれば、スクロール可能な ResultSet オブジェクトを手に入れたのと同じことになります。

もう 1 つの一般的な用途は、ドライバまたは ResultSet オブジェクトを JavaBeans コンポーネントにすることです。JdbcRowSet オブジェクトは、ほかのすべての RowSet オブジェクトと同じく、JavaBeans コンポーネントです。JdbcRowSet オブジェクトは、特定のドライバに継続的に接続されているため、そのドライバのラッパーとしての役割を果たせます。その結果、そのドライバを JavaBeans コンポーネントとして提供できます。これは、JdbcRowSet オブジェクトとして提供されたドライバが、ツール上でアプリケーション構築用として提供される Beans の 1 つになることを意味します。また、継続的に接続されていることは、JdbcRowSet オブジェクトがその ResultSet オブジェクトのラッパーとしての役割を果たせることも意味します。JdbcRowSet オブジェクトは、自身に対して呼び出されたメソッドを、ResultSet オブジェクトに対して呼び出すことができます。その結果、ResultSet オブジェクトを、たとえば、Swing 技術を使用した GUI アプリケーション内のコンポーネントの 1 つとして提供できます。

次のコードは、JdbcRowSet オブジェクトを作成し、そのプロパティを設定し、command プロパティ内のコマンド文字列を実行する方法を示したものです。JdbcRowSet 実装にはデフォルトのコンストラクタが用意されていますが、JdbcRowSet 実装は JavaBeans コンポーネントであるため、おそらくほとんどの場合、インスタンスはビジュアル JavaBeans 開発ツールによって作成されます。

    JdbcRowSet jrs = new JdbcRowSetImpl();
    jrs.setCommand("SELECT * FROM TITLES);
    jrs.setURL("jdbc:myDriver:myAttribute");
    jrs.setUsername("cervantes");
    jrs.setPassword("sancho");
    jrs.execute();
この時点で、jrs には、テーブル TITLES 内のすべてのデータが格納されています。なぜなら、jrs のコマンドによって生成された ResultSet オブジェクトに、テーブル TITLES 内のすべてのデータが格納されているからです。

ここから先は、コード内で単純に ResultSet のメソッドを使用できます。というのも、内部的には ResultSet オブジェクト上ですべての処理が実行されるからです。jrs 内の行間移動、列の値の取得、列の値の更新、新しい行の挿入などの操作を行えます。たとえば、次の 2 行からなるコードでは、ResultSet のメソッドを使って、2 番目の行に移動し、最初の列の値を取得しています。

    jrs.absolute(2);
    String title = jrs.getString(1);

10.2.3     CachedRowSet 実装の概要

CachedRowSet インタフェースの標準実装 (パッケージ com.sun.rowset 内の CachedRowSetImpl) は、データソースの外部にあるメモリ内にキャッシュされた一連の行を格納するためのコンテナであり、切断された行セットであり、直列化、更新、およびスクロールが可能となっています。CachedRowSet オブジェクトは、自身のデータをキャッシュ内に格納するため、データソースとの接続を維持する必要がありません。したがって、データソースとの間でデータの読み書きを行う時以外は、データソースとの接続は切断されています。ただし、CachedRowSet オブジェクトは、そのすべての行をメモリ内に格納するため、非常に大きなサイズのデータセットを格納するには不向きです。とはいえ CachedRowSet オブジェクトでは、大量データにも対応できるように一度に指定された行数のデータだけを読み込んで、データをページングできるようになっています。

CachedRowSet オブジェクトは、特定の表形式データソースから取得したデータを自身に設定できます。また、更新可能であるため、格納されているデータを変更することもできます。CachedRowSet オブジェクトは、ほかのすべての RowSet オブジェクトと同じく、データの取得に加え、データの出力も行えます (変更内容を基となるデータソースに反映させることができる)。

10.2.4     CachedRowSet オブジェクトの使用

CachedRowSet オブジェクトは、接続が切断されていて直列化可能であるため、thin クライアントでの使用に特に適しています。また、データソースに継続的に接続するわけではないため、JDBC ドライバの存在を必要としません。したがって、ResultSet オブジェクトや接続された RowSet オブジェクトに比べて大幅な軽量化が図れます。

CachedRowSet インスタンスの主な用途の 1 つは、アプリケーションサーバ内で動作する EnterpriseJavaBeans TM (EJBTM) コンポーネントなど、分散アプリケーションのコンポーネント間で、表形式のデータをやり取りすることです。サーバは、JDBC API を使ってデータベースから一連の行を取得したあと、そのデータをたとえば Web ブラウザ内で動作している thin クライアントに、CachedRowSet オブジェクトを使ってネットワーク経由で送信することができます。

CachedRowSet オブジェクトのもう 1 つの用途は、スクロール機能および更新機能を持たない ResultSet オブジェクトに、それらの機能を提供することです。たとえば、アプリケーション内で、JdbcRowSet オブジェクトと同様に、ResultSet オブジェクトのデータで初期化された CachedRowSet オブジェクトを作成し、結果セットの代わりにその行セットを使って処理を実行することができます。行セットをスクロールと更新が可能なように設定しておけば、アプリケーションは、カーソルを移動してデータを更新し、その更新をデータソースに反映させることができます。

次のコードでは、CachedRowSet オブジェクト crset に、ResultSet オブジェクト rs のデータを設定しています。

    ResultSet rs = stmt.executeQuery("SELECT * FROM AUTHORS");
    CachedRowSet crset = new CachedRowSetImpl();
    crset.populate(rs);
いったん行セットにデータが設定されれば、アプリケーションは、分散コンポーネントがその行セットを操作できるように、その行セットをネットワーク経由で分散コンポーネントに渡すことができます。また、rs の代わりに crset に対して処理を行うことで、スクロール機能や更新機能を手に入れることができます。

CachedRowSet 実装は、BaseRowSet 抽象クラスの基本 API を使用するとともに、CachedRowSet インタフェースを使用しています。このインタフェースには、必要となる追加機能に対するメソッドが定義されています。たとえば、データソースに接続してデータを読み込む機能が必要となります。また、接続が切断されている間に CachedRowSet オブジェクトのデータが変更される可能性がある場合、データソースに接続して新しいデータを書き戻す機能も必要となります。RowSetReaderRowSetWriter の両インタフェースに基づいて作成され、SyncProvider 実装内にカプセル化されたリーダ/ライター機構が、これらの機能を提供します。

ライター実装は、切断中になされたすべての変更をデータソースに反映し、変更データを持続化するためのメカニズムを提供します。「競合」が存在する場合、つまり、データソース内の同じデータを別のユーザがすでに変更していた場合、そうした処理は複雑になる可能性があります。SyncProvider クラスは、データベース内のデータに対するほかのユーザのアクセスレベルをさまざまに制御するメカニズムを備えており、その結果、発生する競合の数をある程度制御できるようになっています。さらに重要なことは、個々の SyncProvider 実装ごとに、同期時の考慮レベルを決定できるという点です。

JDBC RowSet 実装仕様には、異なるレベルの並行処理をサポートする 2 つの同期用リファレンス実装が含まれています。RIXmlProvider は、競合の有無をチェックせず、変更データをデータソースに単純に書き込みます。RIOptimisticProvider 実装に定義されているライターは、競合の有無をチェックし、競合が存在した場合には、変更データを書き込むかどうかを、アプリケーションが SyncResolver オブジェクトを使ってケースバイケースで決定できるようにします。サードパーティは、さまざまなレベルの同期機能を備えた SyncProvider 実装を作成できます。そして、切断された行セットは、それらのうち、システムの要求に最も合ったものを使用できます。SyncProvider クラスおよび SyncResolver インタフェースについては、「javax.sql.rowset.spi パッケージの使用」を参照してください。

CachedRowSet インタフェースの実装は、ほかの切断された行セットを実装する際の基盤として使えます。たとえば、WebRowSetFilteredRowSetJoinRowSet の各インタフェースの実装はすべて、CachedRowSet インタフェースの実装に基づいて作成されています。したがって、CachedRowSet インタフェースに関する以下のセクションでは、このインタフェース内に定義されているメソッドとそれらの処理内容について、ある程度詳しく説明します。 切断された行セットのその他の実装に関するセクションでは、CachedRowSet 実装に含まれていない機能について説明します。

10.2.5     CachedRowSet オブジェクトの作成

すべての RowSet オブジェクトは JavaBeans コンポーネントです。このため、開発者は通常、アプリケーション構築中にビジュアルな JavaBeans 開発ツールを使ってそれらのオブジェクトを作成します。また、RowSet インタフェースを実装したクラスが提供する public のコンストラクタを使って、アプリケーション内で実行時にインスタンスを作成することも可能です。たとえば、CachedRowSet インタフェースの標準実装には、public のデフォルトコンストラクタが定義されています。したがって、次のコード行を使えば、CachedRowSet のインスタンスを作成できます。

    CachedRowSet crset = new CachedRowSetImpl();
新しく作成された crset は、一連のデータを格納するためのコンテナであり、BaseRowSet オブジェクトのデフォルトのプロパティを含んでいます。この実装は、BaseRowSet クラスを使用しています。このため、crsetBaseRowSet のメソッドを使って、それらのデフォルトプロパティに新しい値を設定したり、その他のプロパティに必要に応じて値を設定したりできます。たとえば、次のコードでは、1 行目で、crset がデータ取得時に使用するコマンド文字列を設定し、2 行目で、エスケープ処理をオフにしています。

    crset.setCommand("SELECT * FROM EMPLOYEES");
    crset.setEscapeProcessing(false);

10.2.6     SyncProvider オブジェクトの設定

すべての CachedRowSet オブジェクトは、リーダとライターを実装した SyncProvider オブジェクトを取得する必要があります。このオブジェクトがないと、データソースからデータを取得したり、変更データをデータソースに書き戻したりできません。SyncFactory は、RowSet オブジェクトからの要求を受け取ると、自身に登録された特定の SyncProvider オブジェクトのインスタンスを作成します。crset を作成したコンストラクタにパラメータが指定されなかったため、SyncFactory から、デフォルトである RIOptimisticProvider (RowSet の標準実装に含まれている、SyncProvider クラスの 2 つの実装のうちの 1 つ) のインスタンスが返されました。

SyncProvider 実装の完全修飾クラス名がコンストラクタに指定された場合、SyncFactory は、その SyncProvider 実装のインスタンスで RowSet オブジェクトを初期化します。ただし、その実装が SyncFactory に登録されている必要があります。次のコード行では、CachedRowSet オブジェクトを作成していますが、その同期プロバイダは、指定された SyncProvider 実装のインスタンスになります。

    CachedRowSet crset = new CachedRowSetImpl("com.supersoftware.providers.HighAvailabilityProvider");
RowSet オブジェクトを作成したあと、そのオブジェクトに別の登録済み SyncProvider 実装を設定することが可能です。そのような実装としては、サードパーティ製の実装や、もう 1 つの標準プロバイダ実装である RIXmlProvider などがあります。なお、RIXmlProvider は、RowSet オブジェクトの読み書きを XML 形式で行います。SyncProvider オブジェクトを設定するには、CachedRowSet のメソッド setSyncProvider を使用します。このメソッドは、現在の SyncProvider オブジェクトを指定されたものに置き換えます。たとえば、crset の現在のプロバイダが RIOptimisticProvider オブジェクトであるとすると、次のコード行によって、プロバイダが RIOptimisticProvider オブジェクトから com.supersoftware.providers.HighAvailabilityProvider オブジェクトに変更されます。

    crset.setSyncProvider("com.supersoftware.providers.HighAvailabilityProvider");

10.2.7     CachedRowSet オブジェクトのデータ設定

CachedRowSet オブジェクトに格納するデータのソースとして、ファイルやスプレッドシートなどのデータソースを選択することも可能ですが、おそらく、そうしたソースとして主に使用されるのは、JDBC ドライバ経由で取得した ResultSet オブジェクトです。RowSet インタフェースには、パラメータを取らないメソッド execute が含まれていますが、CachedRowSet インタフェースには、Connection オブジェクトをパラメータとして取る別バージョンのメソッド execute が含まれています。どちらのメソッドも、CachedRowSet オブジェクトのコマンド実行時に作成された ResultSet オブジェクトのデータを、CachedRowSet オブジェクトに設定します。また、CachedRowSet インタフェースには、追加のメソッドとして populate も含まれています。このメソッドは、ResultSet オブジェクトをパラメータとして取ります。

RowSet インタフェースから継承されたバージョンの execute は、内部的に接続を確立してからでないと、行セットのコマンド文字列を実行できません。CachedRowSet インタフェースに定義されたバージョンの execute は、Connection オブジェクトを渡されるので、独自の接続を確立する必要はありません。メソッド populate は、独自の接続を確立したりクエリーを実行したりする必要はありません。というのも、データの取得先として使える ResultSet オブジェクトを渡されるからです。したがって、メソッド populate を呼び出す前に、接続に関係するプロパティを設定したり、コマンド文字列を設定したりする必要はありません。次のコードは、メソッド populate の使い方を示したものです。

    ResultSet rs = stmt.executeQuery("SELECT NAME, SALARY FROM EMPLOYEES");

    // The code that generates rs
    . . .
    CachedRowSet crset = new CachedRowSetImpl();
    crset.populate(rs);
新しい CachedRowSet オブジェクト crset に含まれているデータは、ResultSet オブジェクト rs に含まれているデータと同一です。

もちろん、メソッド populate の実装詳細は実装間で異なる可能性がありますが、ResultSet オブジェクトを受け取る populate は、いくつかの点でメソッド execute とはまったく異なります。前述したように、populate メソッドでは、接続を確立する必要はありません。また、リーダを使用する必要もありません。指定された結果セットからデータを直接取得できるからです。populate メソッドは、ResultSetMetaData のメソッドを使って結果セットの形式に関する情報を取得できるため、ResultSet オブジェクト内のデータを取得する際に、ResultSet インタフェースの適切な取得メソッドを使用することができます。

メソッド executepopulate には、類似点がいくつかあります。最大の類似点は、どちらのメソッドも行セット全体の内容を変更する、という点です。その結果、どちらのメソッドでも、リスナーへの通知処理、現在の行セット値を元の値に設定する処理、および行セットのメタデータ情報の更新処理が実行されます。

メソッド execute の 2 つのバージョンの使用例を、以下に示します。どちらのバージョンもクエリーを実行します。したがって、どちらの場合もコマンド文字列を設定する必要があります。メソッド execute に接続を渡さなかった場合、適切な接続関連プロパティを設定してからでないと、コマンドを呼び出せません。execute メソッドは、行セットのリーダを呼び出します。すると、そのリーダは、必要なプロパティを使ってデータソースとの接続を確立します。いったん接続が確立されると、リーダは、メソッド executeQuery を呼び出すことで、行セットのコマンド文字列を実行し、データの取得先として使える結果セットを取得できます。

    CachedRowSet crset1 = new CachedRowSetImpl();
    crset1.setCommand("SELECT NAME, SALARY FROM EMPLOYEES");
    crset1.setDataSourceName("jdbc/myDataSource");
    crset1.setUsername("paz");
    crset1.setPassword("p38c3");
    crset1.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

    crset1.execute();
次の例では、メソッド executeConnection オブジェクト con が渡されています。CachedRowSet オブジェクト crset2 は、con を使ってコマンド文字列を実行します。したがって、新しい接続を内部的に確立する必要はありません。

    CachedRowSet crset2 = new CachedRowSetImpl();
    crset2.setCommand("SELECT NAME, SALARY FROM EMPLOYEES");
    crset2.execute(con);
crset1crset2 はどちらも、コマンド文字列を実行し、その結果データを読み込み、それを自身に挿入する間は、データソースに接続されていました。メソッド execute の実行が終了すると、どちらの場合もデータソースへの接続が閉じられます。

10.2.8     データへのアクセス

CachedRowSet オブジェクトは、ほかのすべての行セットと同じく、ResultSet インタフェースから継承した取得メソッドを使ってデータにアクセスします。CachedRowSet オブジェクトはデフォルトでスクロール可能になるように実装されているため、ResultSet のカーソル移動用メソッドを使用することもできます。たとえば、次のコードでは、CachedRowSet オブジェクト crset の最後の行にカーソルを移動したあと、その行の最初の列の String 値を取得しています。

    crset.last();
    String note = crset.getString(1);
CachedRowSet オブジェクトは常に ResultSet.TYPE_SCROLL_INSENSITIVE 型であるように実装されています。したがって、CachedRowSet オブジェクトは、スクロール可能であることに加え、外部による変更を反映しません。これは道理にかなっています。なぜなら、CachedRowSet オブジェクトはほとんどの期間切断された状態にあり、切断されている間は、データの取得先である基となるデータソースに対して外部のユーザが行った変更を知る方法がないからです。

10.2.9     データの変更

前のセクションで説明したように、CachedRowSet オブジェクトの内容全体を変更するには、メソッド executepopulate を使用します。一度に 1 つの行を変更するには、ResultSet の更新メソッドとメソッド insertRowdeleteRow を使用します。JDBC RowSet 実装仕様では、行の挿入位置については規定されていないことに注意してください。このリファレンス実装では現在行の直後に行が挿入されますが、行の挿入位置に関しては、実装者による選択の余地が大いに残されています。

更新メソッドは、現在行の指定された列の値を、指定された値 (通常は 2 番目のパラメータとして渡された値) に変更します。更新メソッドによって変更されるのは、メモリにキャッシュされている行セット内の値だけです。基となるデータソース内の値は変更されません。また、行セット内で元の値として追跡されている値も変更されません。

アプリケーションは、1 つの行に対するすべての更新が完了すると、メソッド updateRow を呼び出します。CachedRowSet 実装では、このメソッドが呼び出されると、現在行の更新が完了したことがシステムに通知されます。ただし、更新メソッドの場合と同じく、基となるデータソース内の値は変更されません。同様に、元の値として格納されている値も、updateRow メソッドでは変更されません。アプリケーションは、更新対象のすべての行に対して updateRow メソッドを呼び出し終わったら、CachedRowSet のメソッド acceptChanges を呼び出す必要があります。このメソッドは、ライターコンポーネントを内部的に呼び出すことで、その行セットの基になっているデータソースに変更を反映したあと、変更された列ごとに現在の値を元の値に設定します。

アプリケーションは、いったん updateRow を呼び出すと、現在行の更新を取り消すためのメソッド cancelRowUpdates を呼び出せなくなります。ただし、メソッド acceptChanges をまだ呼び出していない場合は、メソッド restoreOriginal を呼び出すことができます。このメソッドは、すべての行に対する更新を取り消すために、行セット内の更新された値を元の値に置き換えます。restoreOriginal メソッドの実行には、基となるデータソースとの通信は必要ありません。

次のコードでは、CachedRowSet オブジェクト crset 内の最初の 2 行を更新しています。

    crset.execute();
    // crset is initialized with its original and current values
    
    crset.first();
    crset.updateString(1, "Jane_Austen");
    crset.updateFloat(2, 150000f);
    crset.updateRow();
    // the current value of the first row has been updated
    
    crset.relative(1);
    crset.updateString(1, "Toni_Morrison");
    crset.updateFloat(2, 120000f);
    crset.updateRow();
    // the current value of the second row has been updated
    
    crset.acceptChanges();
    // the original value has been set to the current value and the 
    // database has been updated

10.2.10     リーダとライターのカスタマイズ

切断された RowSet の実装では、行セットフレームワークが提供するデータ取得/更新機能をカスタマイズすることができます。これは、CachedRowSet オブジェクトなどの切断された行セットに適用されます。というのも、切断された行セットでは、リーダとライターのサービスが必要となりますが、それらのサービスは SyncProvider オブジェクトによってカプセル化されているからです。

CachedRowSet インタフェースには、CachedRowSet オブジェクトにリーダとライターの実装を設定するためのメソッド setSyncProvider が定義されています。また、行セットの SyncProvider オブジェクトを取得するためのメソッド getSyncProvider も定義されています。行セットのリーダとライターは、完全にシステム内部で動作し、任意の数のタスクを実行しますが、それらのタスクをカスタマイズすれば、追加機能を提供できます。

RowSetReader インタフェースに含まれる単一のメソッド readData (public メソッド) は、さまざまな方法でカスタマイズ可能です。たとえば、JDBC ドライバを使ってデータベースから読み込む代わりに、ファイルその他の非 SQL データソースからデータを直接読み込むように、リーダを実装してもかまいません。そのようなリーダは、メソッド RowSet.insertRow を使って新しい行を行セットに挿入できます。また、このメソッドは、リーダから呼び出された場合には行セット内の元の値を更新するように実装することもできます。

RowSetWriter インタフェースに含まれる唯一のメソッド (public メソッド) writeData は、変更されたデータを基となるデータソースに書き戻します。このメソッドも、さまざまな方法でカスタマイズできます。ライターはリーダと同じく、データソースとの接続を確立します。ただし、競合の有無をチェックするかどうかは、実装方法によって異なります。RowSetInternal のメソッド getOriginalgetOriginalRow は、現在の変更前に存在していた値を返します。したがって、ライターは、それらの値をデータソースから読み込んだ値と比較することで、そのデータソースが変更されたかどうかを判断できます。競合が存在している場合にデータを書き込むかどうかを、ライターがどのように判断するかは、やはり実装方法によって異なります。

カスタマイズされたリーダやライターと連携するように設定された CachedRowSet オブジェクトは、通常の JavaBeans コンポーネントとして提供できます。したがって、アプリケーションを作成する開発者は、リーダやライターのカスタマイズについて考慮せずにすみ、行セットをいかに効果的に使用するかという、より重要な側面に集中できるようになります。

10.2.11     その他のメソッド

CachedRowSet インタフェースには、ほかにも多くのメソッドが定義されています。たとえば、2 つのバージョンのメソッド toCollection が定義されています。行セット内のデータをコレクション内の要素として処理したほうが便利な場合があります。その場合、これらのメソッドは行セットのデータを Java のコレクションに変換します。CachedRowSet には、行セットのコピーを作成するメソッドも用意されています。CachedRowSet.cloneCachedRowSet.createCopy は、指定された行セットとまったく同じコピーを作成します。なお、作成されたコピーは元の行セットから独立しています。これに対し、CachedRowSet.createShared メソッドは、元の行セットと状態を共有する行セットを作成します。言い換えると、新しい行セットと元の行セットは、物理的に同一のメモリ領域 (元の値と現在の値) を共有します。状態を共有している行セットの一方で更新メソッドが呼び出された場合、その更新内容は他方にも反映されます。結果的に、createShared メソッドを使えば、単一の行セットに対して複数のカーソルを作成できることになります。

10.2.12     javax.sql.rowset.spi パッケージの使用

パッケージ javax.sql.rowset.spi は、開発者が同期プロバイダを実装する際に使用する必要のある API を提供します。このパッケージに含まれているクラスとインタフェースは、次のとおりです。

次の 2 つのインタフェースは、パッケージ javax.sql から継承されています。

CachedRowSet オブジェクトまたはその他の切断された RowSet オブジェクトは、自身の SyncProvider オブジェクトを SyncFactory オブジェクトから取得します。このクラスは static であるため、SyncFactory の実体は 1 つしか存在しません。つまり、切断された RowSet オブジェクトが自身の SyncProvider オブジェクトの取得先として使えるソースは、1 つだけです。各ベンダーが SyncProvider 抽象クラスの実装を SyncFactory に登録すると、各 RowSet オブジェクトがその実装をプラグインできるようになります。 次のコード行は、SyncProvider 実装を SyncFactory に登録する方法の 1 つを示したものです。

    SyncFactory.registerProvider("com.supersoftware.providers.HAProvider");

registerProvider に指定した引数が、SyncProvider 実装の完全修飾クラス名であることに注意してください。

SyncProvider 実装を登録する別の方法として、その実装をシステムプロパティに追加するという方法もあります。この方法では、実行時にコマンド行から次のように入力します。

    -Drowset.provider.classname=com.supersoftware.providers.HAProvider
SyncProvider 実装を登録する別の方法として、完全修飾クラス名、ベンダー、およびバージョン番号を標準プロパティファイルに追加するという方法もあります。リファレンス実装に付属するプロパティファイルには、2 つのリファレンス実装同期プロバイダ RIOptimisticProviderRIXmlProvider に対するエントリが、最初から含まれています。SyncProvider 実装の 4 つ目の登録方法は、目的の実装を特定の JNDI コンテキストとして登録したあと、その JNDI コンテキストを SyncFactory に登録するという方法です。その際、完全修飾プロバイダ名をメソッド SyncFactory.registerJNDIContext に指定します。

RowSet オブジェクトが特定の SyncProvider 実装を要求すると、SyncFactory は、その実装を検索してインスタンスを作成し、それを RowSet オブジェクトに返します。たとえば、次のコード行では、CachedRowSet オブジェクト crset が、com.supersoftware.providers.HAProvider を要求しています。

    crset.setSyncProvider("com.supersoftware.providers.HAProvider");
要求されたプロバイダが使用可能ないずれの方法でも登録されていなかった場合、SyncFactorySyncFactoryException オブジェクトをスローします。

CachedRowSet オブジェクトまたはその任意のサブクラスは、どのような SyncProvider 実装が SyncFactory に登録されていて、利用可能になっているかを知ることができます。それには、次のコード行を呼び出します。

    java.util.Enumeration providers = SyncFactory.getRegisteredProviders();
SyncProvider オブジェクトの主要コンポーネントは、オブジェクトごとに実装されているリーダとライターです。リーダの実装によって、データの読み込み先として使えるデータソースの種類 (リレーショナルデータベース、フラットファイル、スプレッドシートなど) とその読み込み方法が決まります。ライターの実装によって、使用される並行処理のレベル、競合の有無をチェックするかどうか、および検出した競合の処理方法が決まります。RIOptimisticProvider に含まれるリーダ (RowSetReader インタフェースを実装したもの) は、リレーショナルデータベースからデータを読み込みます。ライター (RowSetWriter インタフェースを実装したもの) は、競合の有無をチェックし、競合が見つかった場合はデータソースにデータを書き込みません。RIXmlProvider 内に実装されているリーダとライターは、行セットを XML ドキュメントとして読み書きします。このため、このプロバイダは主に WebRowSet オブジェクトによって使用されます。ライターは、競合の有無をチェックせず、RowSet オブジェクトのすべての変更をデータソースに単純に書き込みます。

TransactionalWriter インタフェースを使えば、グローバルまたはローカルのトランザクションに参加している CachedRowSet オブジェクトは、トランザクション境界に対するきめ細かな制御を行えるようになります。この機能を利用したい場合、CachedRowSet オブジェクトは、TransactionalWriter インタフェースを実装した SyncProvider オブジェクトを使用する必要があります。

リファレンス実装には、アプリケーションが競合をケースバイケースで解決できるようにするための手段が用意されています。ライターは、すべての競合を検出し終わると、SyncProviderException オブジェクトをスローします。アプリケーションは、その例外をキャッチし、それを基に SyncResolver オブジェクト (特殊化された RowSet オブジェクト) を作成できます。SyncResolver オブジェクトは、競合が存在している RowSet オブジェクトに対応しており、それと同じ数の行と列を含んでいますが、そこに含まれているのは、競合が存在しているデータだけです。

SyncResolver オブジェクトは、特定の行に存在する競合値を 1 つずつ取り出し、データソース内の値と RowSet オブジェクト内の値を比較します。SyncResolver オブジェクトは、その行のすべての競合について、どの値を持続するかを決定すると、それらの値をメソッド setResolvedValue を使って設定します。続いて、SyncResolver オブジェクトは、次の競合に移動し、競合がなくなるまで以上の処理を繰り返します。

10.2.13     CachedRowSet のまとめ

以上をまとめると、CachedRowSet インタフェースの実装は、接続が切断されており、ドライバがなくても動作可能です。また、特に thin クライアント上で分散アプリケーションを使ってデータを受け渡したり、結果セットをスクロール可能/更新可能にできるように設計されています。このほかにも、さまざまな目的のために、多数の RowSet 実装を設計することが可能です。

10.2.14     WebRowSet 実装

WebRowSet インタフェースは、CachedRowSet インタフェースを拡張したものであり、CachedRowSet に含まれるすべての機能と同じものを備えています。追加されたのは、行セットを XML 形式で読み書きする機能です。WebRowSet オブジェクトは、WebRowSetXmlReader オブジェクトを使って XML 形式の行セットを読み込み、WebRowSetXmlWriter オブジェクトを使って行セットを XML 形式で書き込みます。XML 版の WebRowSet オブジェクトには、データのほかに、プロパティを含むメタデータも含まれています。

WebRowSet オブジェクトが行セットを XML ドキュメントとして読み書きできるように、WebRowSet のデフォルトコンストラクタは RIXmlProvider 実装を要求します。また、XML 機能を備えたサードパーティ製の SyncProvider 実装を、WebRowSet オブジェクトに設定することもできます。

WebRowSet オブジェクトは、分散クライアント/サーバアプリケーション内でうまく動作するように設計されています。CachedRowSet 実装との類似点としては、どちらも thin クライアントとアプリケーションサーバを接続する役割を果たす点が挙げられます。したがって、どちらも、thin クライアントにデータを送信する用途に適しています。両者の相違点は、使用するプロトコルが異なることです。CachedRowSet オブジェクトは直列化可能であるため、RMI/IIOP (Remote Method Invocation/Internet Interoperability Protocol) を使って別のコンポーネントに送信することが可能です。WebRowSet オブジェクトは、HTTP/XML (Hypertext Transfer Protocol/eXtensible Markup Language) を使って中間層と通信することで、たとえば、Web クライアントがデータアクセスを提供する Java サーブレットとやり取りできるようにします。

XML が Web サービスにとってますます重要になってきている理由は、データの移植性の高さにあります。JDBC RowSet 実装仕様には、標準 WebRowSet の XML Schema (http://java.sun.com/xml/ns/jdbc/webrowset.xsd) が含まれており、標準 WebRowSet オブジェクトはこのスキーマに準拠しています。このため、通信する 2 者が WebRowSet オブジェクトの XML スキーマを所持していれば、両者はそのスキーマに基づく共通の形式を使って行セットを交換できます。その際、両者のデータが内部的にはまったく異なる形式で保存されていたとしても、何の問題もありません。したがって、WebRowSet オブジェクトは強力なデータ交換ツールとして使えます。

Java プラットフォームはコードの移植性を提供し、XML はデータの移植性を提供します。したがって、両者は、Web サービスを実現するための理想的な組み合わせです。JavaTM API for XML-based RPC、SOAP with Attachments API for JavaTM、JavaTM Architecture for XML Binding、JavaTM API for XML Registries などの技術を活用すれば、Web サービスの開発がますます容易に行えるようになります。さらに、J2EE プラットフォームが提供するインフラストラクチャを使えば、開発者は、分散トランザクション、接続プール、およびセキュリティ用の管理ソフトウェアを独自に作成する必要がなくなります。

データ送信用に WebRowSet オブジェクトを使用できることは、Java プラットフォームを使って Web サービスを実現する際のメリットを、さらに高めてくれます。たとえば、WebRowSet オブジェクトは、標準の XML スキーマを使用しているため、Web サービスメッセージの一部にすることが可能です (通常は SOAP with Attachments API for Java 技術を使用する)。

次の単純なサンプルコードでは、WebRowSet オブジェクト wrs を作成し、それに ResultSet オブジェクト rs のデータを設定したあと、特定の列の値を更新しています。最後の 2 行では、wrs を XML 形式で出力しています。

    WebRowSet wrs = new WebRowSetImpl();
    wrs.populate(rs);
    
    //perform updates
    wrs.absolute(2)
    wrs.updateString(1, "newString");
    
    FileWriter fWriter = new FileWriter("/share/net/output.xml");
    wrs.writeXml(fWriter);
この XML 出力を受信したマシンでは、次のような処理によってデータが読み込まれます。いったん fReader から wrs へのデータとメタデータの読み込みが完了すると、wrs は、コード内でほかの WebRowSet オブジェクトとまったく同様に処理されます。

    WebRowSet wrs = new WebRowsetImpl();
    
    FileReader fReader = new FileReader("/share/net/output.xml");
    wrs.readXml(fReader);
    
    wrs.absolute(2);
    String str = wrs.getString(1);

10.2.15     FilteredRowSet 実装

FilteredRowSet オブジェクトは、CachedRowSet オブジェクトを拡張したものです。プログラマはこのオブジェクトを使うことで、行セットをフィルタリングしたサブセットデータを使用できるようになります。たとえば、FilteredRowSet オブジェクトに非常に大量の行が含まれているとします。それらの行のサブセットに対してのみ処理を行いたいプログラマは、値の範囲を指定するだけで、FilteredRowSet オブジェクトからその範囲に含まれる値のみが返されるようになります。こうしたフィルタリングは、メソッド next 内で実行されます。このメソッドは、指定された範囲に含まれない行をすべて読み飛ばすように実装されています。この機能がなければ、プログラマは、データソースへの接続を確立し、選択された行を含む ResultSet オブジェクトを取得し、それらの行を行セットに設定しなければなりません。接続の確立には非常にコストがかかります。FilteredRowSet オブジェクトの格納に必要とされるメモリのコストとは比較になりません。したがって、新しい接続を確立せずに選択された行セットを取得する機能は、多くの場合、パフォーマンスの大幅な向上をもたらします。

ここで、企業 XYZ のすべての従業員を含む FilteredRowSet オブジェクト frs があるとします。今、この企業に含まれるすべての従業員の平均給与額を計算し終わったところです。次に、名前が「Aaronson」から「Lee」の範囲に含まれる従業員の情報だけを取得したいとします。次のコードでは、列 NAMES の値が「Aaronson」から「Lee」の範囲に含まれている行だけが、frs から返されるようにしています。

次のコードでは、FilteredRowSet オブジェクト frs に、ResultSet オブジェクト rs 内のデータを設定しています。続いて、Range オブジェクトを作成しています。このオブジェクトは、列 NAME に含まれる「Aaronson」から「Lee」までの姓の範囲が、メソッド next から返される名前の範囲になることを指定しています。

    FilteredRowSet frs = new FilteredRowSetImpl();
    frs.populate(rs);
    
    Range names = new Range("Aaronson", "Lee", findColumn("NAME"));
    frs.setFilter(names);
    
    while (frs.next()) {
      String name = frs.getString("NAME");
      . . . // add each name to, for example, a mailing list
      // only names from "Aaronson" to "Lee" will be returned
    }
FilteredRowSet オブジェクトに格納されたサブセット内のデータの一部を変更する必要がある場合、それらの変更をデータソースに同期させることができます。サブセット内の変更だけが同期されます。ただし、データのソースが、更新不可能な SQL VIEW (定数 SyncProvider.NONUPDATABLE_VIEW_SYNC で表される) である場合、FilteredRowSet オブジェクトへの変更は同期されません。

10.2.16     JoinRowSet 実装

JoinRowSet オブジェクトを使えば、プログラマは異なる 2 つの RowSet オブジェクト内のデータを結合することができます。この機能は特に、関連データが複数のデータソースに格納されている場合に便利です。結合には任意の RowSet 実装を使用できますが、通常結合されるのは、2 つの CachedRowSet オブジェクトです。すべての関連データを結合して単一の JoinRowSet オブジェクトとして表現すれば、アプリケーションは、ほかの種類の RowSet オブジェクトとまったく同様に、そのデータを処理できます。

次のコードは、2 つの CachedRowSet オブジェクトを結合して単一の JoinRowSet オブジェクトとして表現する方法を示したものです。このシナリオでは、テーブル EMPLOYEES 内のデータとテーブル BONUS_PLAN 内のデータを結合することで、各従業員の個人情報とボーナス計画情報のすべてを、単一の行セットとして提供しています。元の行セットはどちらも、最初の列に従業員識別番号を含んでいるため、両テーブル内のデータを結合する際にその最初の列が使用されています。結合後、JoinRowSet オブジェクトの各行には、両方の行セットの列からなる、同一従業員 ID に関連付けられたデータが含まれています。

コードの 1 行目では、JoinRowSet オブジェクト jrs を作成しています。続いて、テーブル EMPLOYEES 内のすべての列のデータを新しい CachedRowSet オブジェクト empl に設定し、empl の最初の列を一致列として設定し、その empljrs に追加しています。続いて同様に、テーブル BONUS_PLAN 内のすべての列のデータを CachedRowSet オブジェクト bonus に設定し、bonus の最初の列を一致列として設定し、その bonusjrs に追加しています。emplbonus のどちらも、最初の列は EMPLOYEE_ID (主キー) です。この列は、2 つの行セットに共通しているので、empl 内の特定の従業員の情報と、bonus 内の同じ従業員に対する情報を一致させるために使用できます。最後のコード行では、jrs 内を移動し、特定の列の値を取得しています。

    JoinRowSet jrs = new JoinRowSetImpl();
    
    ResultSet rs1 = stmt.executeQuery("SELECT * FROM EMPLOYEES");
    CachedRowSet empl = new CachedRowSetImpl();
    empl.populate(rs1);
    empl.setMatchColumn(1); // The first column is EMPLOYEE_ID 
    jrs.addRowSet(empl);
    
    ResultSet rs2 = stmt.executeQuery("SELECT * FROM BONUS_PLAN");
    CachedRowSet bonus = new CachedRowSetImpl();
    bonus.populate(rs2);
    bonus.setMatchColumn(1); // The first column is EMPLOYEE_ID 
    jrs.addRowSet(bonus);
    
      // The jrs instance now joins the two rowsets.The application
      // can browse the combined data as if it were browsing one single
      // RowSet object.
    
    jrs.first();
    int employeeID = jrs.getInt(1);
    String employeeName = jrs.getString(2);
JoinRowSet オブジェクトでも FilteredRowSet オブジェクトの場合と同様に、変更データを同期させることができます。ただし、その場合、データの取得先が更新不可能な SQL VIEW であってはなりません。

    
    

[目次] [前の項目] [次の項目]

jdbc@eng.sun.com または jdbc-business@eng.sun.com
Copyright © 1996 -2004 Sun Microsystems, Inc. All rights reserved.