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 (グラフィカルユーザインタフェース) コンポーネントです。行セット内でイベントが発生するたびに、リスナーは通知を受け取ります。したがって、リスナーと行セットとの間で、カーソル位置やデータの一貫性を維持できます。
インタフェース RowSetInternal
、RowSetReader
、RowSetWriter
は、行セットのリーダ/ライター機構をサポートします。「リーダ」とは、
RowSetReader
インタフェースを実装したクラスのインスタンスのことです。リーダは、データを読み込み、それを行セットに挿入します。「ライター」とは、RowSetWriter
インタフェースを実装したクラスのインスタンスのことです。ライターは、行セット内の変更されたデータを、その取得先であるデータソースに書き戻します。
RowSetInternal
インタフェースが提供する追加のメソッドを使えば、リーダまたはライターは行セットの内部状態を操作できます。たとえば、行セットでは元の値を追跡できるようになっていますが、ライターは RowSetInternal
のメソッドを使うことで、データソース内の対応するデータがほかのユーザによって変更されたかどうかをチェックすることができます。さらに、RowSetInternal
のメソッドを使えば、行セットのコマンド文字列用の入力パラメータや行セットに渡された接続が存在している場合、それらの情報を取得することもできます。最後に、リーダは RowSetInternal
のメソッドを使うことで、その行セットに挿入する行を記述した新しい RowSetMetaData
オブジェクトを、その行セットに設定できます。このインタフェースには RowSetInternal
という名前が付けられていますが、この命名は適切です。そのメソッドは、内部的に使用されるのみで、アプリケーションから直接呼び出されることはないからです。
行セットは、「接続された行セット」、「切断された行セット」のいずれかです。接続された RowSet
オブジェクトは、使用されている限り、常にデータソースへの接続を維持します。これに対し、切断された行セットは、データソースとの間でデータの読み書きを行う間だけ、データソースに接続します。接続が切断されている間、行セットは、JDBC ドライバや JDBC API の実装全体を必要としません。そうした行セットは非常に軽量であり、一連のデータを thin クライアントに送信する場合のコンテナとして理想的です。クライアントは必要であれば、データを更新し、その行セットをアプリケーションサーバに送り返すことができます。サーバ上の切断された RowSet
オブジェクトは、自身のライターを使ってデータソースへの接続を確立し、データを書き戻します。その具体的な実行方法は、ライターがどのように実装されているかによります。ライターは通常、接続を確立してデータを読み込む処理を、JDBC ドライバに委譲します。
RowSet
のイベントモデルを使えば、Java オブジェクトまたはコンポーネントは、特定の RowSet
オブジェクトによって生成されたイベントに関する通知を受け取れます。その通知メカニズムの設定には、通知を受け取るコンポーネントと RowSet
オブジェクト自身の両方が関係します。まず、イベント通知を受け取る各コンポーネントは、RowSetListener
インタフェースを実装する必要があります。続いて、各コンポーネントを RowSet
オブジェクトに登録する必要があります。それには、各コンポーネントをそのオブジェクトのイベント通知コンポーネントリストに追加します。この時点で、それらの各コンポーネントはリスナーになります。リスナーとは、特定の RowSet
オブジェクトに登録された、RowSetListener
のメソッドを実装したクラスのインスタンスのことです。
RowSet
オブジェクト内で発生する可能性のあるイベントは、3 種類あります。「カーソルが移動された」、「特定の行が変更された (挿入、削除、更新のいずれか)」、「内容全体が変更された」の 3 つです。これらのイベントにそれぞれ対応するのが、RowSetListener
のメソッド cursorMoved
、rowChanged
、rowSetChanged
です。特定のイベントが発生すると、行セットは、自身をイベント発生元として指定した RowSetEvent
オブジェクトを生成します。各リスナー上の対応する RowSetListener
のメソッドが呼び出されますが、その際、RowSetEvent
オブジェクトがそれらのメソッドに渡されます。こうして、行セットのすべてのリスナーにイベントが通知されます。
たとえば、円グラフのコンポーネント pieChart
に RowSet
オブジェクト rset
のデータを表示させたい場合、RowSetListener
のメソッド cursorMoved
、rowChanged
、および rowSetChanged
を、pieChart
内に実装する必要があります。これらのメソッドの実装によって、rset
でイベントが発生した際の、pieChart
の実行内容が決まります。これらのメソッドの実装が完了すると、pieChart
を rset
に登録できます。pieChart
は、rset
にリスナーとして追加されると、rset
でのイベント発生時に通知を受け取りますが、それは、RowSetEvent
オブジェクトをパラメータとして指定して対応するメソッドを呼び出すことで実現されます。その後、リスナー pieChart
は、自身を更新して rset
の現在のデータとカーソル位置を反映できます。pieChart
に反映する必要のない rset
イベントが存在する場合、そのイベントに対応する RowSetListener
のメソッドを、何も処理しないように実装します。たとえば、rset
の現在のカーソル位置を pieChart
で表示する必要がない場合、メソッド cursorMoved
内で何も処理しないようにします。
ある特定の RowSet
オブジェクトのリスナーとして指定可能なコンポーネントの数に制限はありません。たとえば、棒グラフのコンポーネント barGraph
にも rset
のデータを表示させたい場合、そのコンポーネントをリスナーにすることができます。それには、RowSetListener
のメソッドを実装してから、そのコンポーネントを rset
に登録します。次のコード行では、2 つのコンポーネント pieChart
と barGraph
を、rset
のリスナーとして登録しています。
rset.addRowSetListener(pieChart); rset.addRowSetListener(barGraph);リスナーの削除は、メソッド
RowSet.removeListener
を使って似たような方法で行えます。
rset.removeRowSetListener(pieChart); rset.removeRowSetListener(barGraph);行セットのリスナーを設定するコードは何らかのツールによって生成されることが多いため、アプリケーションプログラマが行う必要があるのは、行セットを指定することと、その行セットでイベントが発生した際の通知を受け取るコンポーネントを指定することだけです。リスナーの設定が完了すると、イベント処理が実行されますが、それは主にシステム内部で実行されます。たとえば、あるアプリケーションが
rset
内の特定の行を更新した場合、rset
は内部的に、RowSetEvent
オブジェクトを作成し、pieChart
と barGraph
に実装されている rowChanged
メソッドに、そのオブジェクトを渡します。リスナーはイベントの発生元を知ることができます。なぜなら、rowChanged
に渡される RowSetEvent
オブジェクトは、rset
(イベントの発生元である RowSet
オブジェクト) で初期化されているからです。コンポーネント pieChart
と barGraph
は、それぞれ独自の RowSetListener.rowChanged
メソッド実装に従って、行の表示を更新します。
次のコードでは、pieChart
と barGraph
を、RowSet
オブジェクト rset
のリスナーとして登録しています。メソッド execute
の呼び出しによって rset
に新しいデータが格納されると、イベント通知が内部的に実行されます (メソッド execute
の詳細については、「コマンドの実行」を参照)。イベント通知処理の最初のステップとして、RowSetEvent
オブジェクト rsetEvent
が生成され、rset
で初期化されます。続いて、pieChart
と barGraph
のバージョンのメソッド rowSetChanged
が、引数に rsetEvent
を指定して呼び出されます。これによって、リスナー pieChart
と barGraph
は、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);
RowSet
インタフェースには一連の JavaBeans プロパティが含まれているため、データソースに接続して一連の行を取得するように RowSet
インスタンスを設定することが可能です。実装にもよりますが、すべてのプロパティが必要であるとは限りません。たとえば、接続の確立時に必要なのは、URL、データソース名のいずれかであるため、一方のプロパティが設定されていれば、他方は省略可能です。両方が設定されていた場合、後から設定されたほうが使用されます。コマンドをサポートしない非 SQL データソース (スプレッドシートなど) から行セットのデータを取得する場合、command プロパティを設定する必要はありません。デフォルト値が設定されているプロパティの場合、デフォルト値で問題なければ設定を省略できます。たとえば、エスケープ処理はデフォルトでオンになっているため、オフにしたい場合を除けば、アプリケーション内でエスケープ処理を設定する必要はありません (エスケープ処理については、「Statement 内の SQL エスケープ構文」を参照)。
以下に示すのは、RowSet
オブジェクトのプロパティの取得/設定用として、RowSet
インタフェース内に定義されている取得/設定メソッドの一覧です。ただし、メソッド getConcurrency
と getType
の 2 つは例外です。これらは、RowSet
インタフェース内に定義されたメソッドではなく、ResultSet
インタフェースから継承されたメソッドです。
getCommand |
setCommand |
---|---|
ResultSet.getConcurrency | setConcurrency |
getDataSourceName | setDataSourceName |
getEscapeProcessing | setEscapeProcessing |
getMaxFieldSize | setMaxFieldSize |
getMaxRows | setMaxRows |
getPassword | setPassword |
getQueryTimeout | setQueryTimeout |
getTransactionIsolation | setTransactionIsolation |
ResultSet.getType | setType |
getTypeMap | setTypeMap |
getUrl | setUrl |
getUsername | setUsername |
これに加え、JDBC RowSet
の標準実装には、それらの実装に固有のプロパティが数多く含まれています。
次のコードでは、DataSource
オブジェクトを使ってデータソースとの接続を確立する際に通常必要とされるプロパティを設定しています (rset
は RowSet
オブジェクト)。
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");
上記のコマンド文字を設定してから rset
の execute
メソッドを呼び出すと、そのクエリーによって生成される結果セットとまったく同じデータ (テーブル EMPLOYEES
内の各行に対して 1 行ずつ。各行には名前と給与額が含まれる) が、rset
内に格納されます。
PreparedSatement
オブジェクトとして表現されています。ただし、このパラメータは出力パラメータではなく入力パラメータである必要があります。
新しく設定された RowSet
オブジェクトにはデータが格納されていないため、メソッド execute
、populate
のいずれかを呼び出してデータを生成する必要があります。メソッド 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
メソッドを呼び出します。
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
を呼び出してカーソルの現在位置を取得し、その行に対するリスナーの表示内容を更新するようにしてもかまいません。
RowSet
と ResultSet
のカーソル移動が同じであることを示すために、次のコードでは、RowSet
オブジェクト rset
に対して順方向のループ処理を行い、各行から取得した 2 つの値を出力しています。
rset.beforeFirst(); while (rset.next()) { System.out.println(rset.getString(1) + " " + rset.getFloat(2)); }その他のカーソル移動も、
ResultSet
インタフェースの場合と同じです。
RowSet
インタフェースに含まれるメソッド execute
は、RowSet
オブジェクトにデータを格納する場合に呼び出されます。このメソッドの実装には多くのバリエーションがあり、サブタイプではデータ設定用の追加メソッドが定義されている可能性があります。execute
メソッドは、行セットのプロパティを使用しますが、必要なプロパティが設定されていなかった場合、SQLException
をスローします。標準プロパティが定義されていますが、RowSet
の実装によっては、ほかのプロパティも定義されている可能性があります。したがって、アプリケーション開発者は、使用する実装のドキュメントによく目を通してください。
切断された行セットでは、リーダ (RowSetReader
インタフェースを実装したオブジェクト) とライター (RowSetWriter
インタフェースを実装したオブジェクト) が必要になります。JDBC RowSet
実装では、javax.sql.rowset.spi
パッケージの SyncProvider
クラス内に、リーダとライターがカプセル化されています。SPI (Service Provider Interface) と呼ばれるこのパッケージについては、後述します。SPI を使えば、リーダとライターの実装および配置が、より簡単に行えます。2 つのリファレンス実装 RIOptimisticProvider
と RIXmlProvider
は、すぐに使える状態になっています。
また、切断された行セットは、その内部状態にリーダとライターからアクセスできるように、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
オブジェクトに対する変更はデータソースにも反映されるため、競合が発生する可能性はありません。
RowSet
オブジェクトは、格納するデータの列に関する一連のメタデータを保持します。RowSet
は ResultSet
から派生したインタフェースであるため、ResultSetMetaData
のメソッドを使えば、ResultSet
オブジェクトのメタデータを取得するのと同じ要領で、RowSet
オブジェクトのメタデータを取得できます。たとえば、次のコードでは、RowSet
オブジェクト rset
に対する RowSetMetaData
オブジェクトを作成し、rset
に格納されている列の個数をチェックしています。ここで、メソッド getMetaData
が ResultSet
のメソッドであり、その戻り値は 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
を呼び出して rowsetmd
を rowset
のメタデータとして設定しています。
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);
RowSet
インタフェースの 5 つの標準実装が含まれています。これらの実装は、独自の実装の開発を目指すユーザを支援する目的で提供されています。RowSet
インタフェースは、さまざまな目的のためにさまざまな方法で実装可能です。また、そうした実装を誰が行ってもかまいません。ただし、RowSet
実装の多くがドライバベンダーによって開発され、それらの実装が JDBC 製品の一部として提供されることが予想されます。実装者は、リファレンス実装をそのまま使用してもかまいませんし、それらを基に、あるいはまったくゼロから、独自の実装を作成してもかまいません。
標準実装は 2 つの部分、すなわちインタフェースとリファレンス実装とで構成されています。インタフェースは javax.sql.rowset
パッケージに、実装は com.sun.rowset
パッケージに、それぞれ収められています。これらは、Java Community Process の JSR 114 を通じ、データベース分野の専門家の意見を基に開発されました。その目的は、行セットの主要機能を標準化し、開発者がそれらの標準機能を独自の実装内で活用できるようにすることでした。RowSet
の標準実装を、次に列挙します。
RowSet
のすべての標準実装は、抽象クラス BaseRowSet
を拡張するとともに、特定のインタフェース (JdbcRowSet
、CachedRowSet
、WebRowSet
、FilteredRowSet
、JoinRowSet
のいずれか) を実装しています。BaseRowSet
クラスは、すべての RowSet
オブジェクトに共通する機能の基本実装を提供しますが、このクラスを使用するかどうかは、開発者に任されています。BaseRowSet
クラスには次のものが含まれています。
RowSet
オブジェクトの command プロパティ内のプレースホルダパラメータを設定するための完全なメソッドセットRowSet
実装に含まれるデフォルト値は次のとおり RowSet
オブジェクトのコマンド実行に費やす時間に制限はないBINARY
、VARBINARY
、LONGVARBINARY
、CHAR
、VARCHAR
、または LONGVARCHAR
の列に格納できるバイト数に制限はないnull
に設定されているRowSet
オブジェクトのコマンドに含まれるプレースホルダパラメータに対する設定値を格納するための、空の Hashtable
オブジェクトを備えている
前述したように、RowSet
オブジェクトは、接続された行セット、切断された行セットのいずれかです。RowSet
実装は次のように分類できます。
JdbcRowSet
CachedRowSet
インタフェースには、切断された RowSet
オブジェクトで必要となるメソッドが含まれています。標準実装の切断された RowSet
は、BaseRowSet
クラスを拡張するとともに、CachedRowSet
インタフェースを実装しています。これに加え、JoinRowSet
実装は JoinRowSet
インタフェースを実装し、FilteredRowSet
実装は FilteredRowSet
インタフェースを実装し、WebRowSet
実装は WebRowSet
インタフェースを実装しています。
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);
CachedRowSet
インタフェースの標準実装 (パッケージ com.sun.rowset
内の CachedRowSetImpl
) は、データソースの外部にあるメモリ内にキャッシュされた一連の行を格納するためのコンテナであり、切断された行セットであり、直列化、更新、およびスクロールが可能となっています。CachedRowSet
オブジェクトは、自身のデータをキャッシュ内に格納するため、データソースとの接続を維持する必要がありません。したがって、データソースとの間でデータの読み書きを行う時以外は、データソースとの接続は切断されています。ただし、CachedRowSet
オブジェクトは、そのすべての行をメモリ内に格納するため、非常に大きなサイズのデータセットを格納するには不向きです。とはいえ CachedRowSet
オブジェクトでは、大量データにも対応できるように一度に指定された行数のデータだけを読み込んで、データをページングできるようになっています。
CachedRowSet
オブジェクトは、特定の表形式データソースから取得したデータを自身に設定できます。また、更新可能であるため、格納されているデータを変更することもできます。CachedRowSet
オブジェクトは、ほかのすべての RowSet
オブジェクトと同じく、データの取得に加え、データの出力も行えます (変更内容を基となるデータソースに反映させることができる)。
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
オブジェクトのデータが変更される可能性がある場合、データソースに接続して新しいデータを書き戻す機能も必要となります。RowSetReader
、RowSetWriter
の両インタフェースに基づいて作成され、SyncProvider
実装内にカプセル化されたリーダ/ライター機構が、これらの機能を提供します。
ライター実装は、切断中になされたすべての変更をデータソースに反映し、変更データを持続化するためのメカニズムを提供します。「競合」が存在する場合、つまり、データソース内の同じデータを別のユーザがすでに変更していた場合、そうした処理は複雑になる可能性があります。SyncProvider
クラスは、データベース内のデータに対するほかのユーザのアクセスレベルをさまざまに制御するメカニズムを備えており、その結果、発生する競合の数をある程度制御できるようになっています。さらに重要なことは、個々の SyncProvider
実装ごとに、同期時の考慮レベルを決定できるという点です。
JDBC RowSet
実装仕様には、異なるレベルの並行処理をサポートする 2 つの同期用リファレンス実装が含まれています。RIXmlProvider
は、競合の有無をチェックせず、変更データをデータソースに単純に書き込みます。RIOptimisticProvider
実装に定義されているライターは、競合の有無をチェックし、競合が存在した場合には、変更データを書き込むかどうかを、アプリケーションが SyncResolver
オブジェクトを使ってケースバイケースで決定できるようにします。サードパーティは、さまざまなレベルの同期機能を備えた SyncProvider
実装を作成できます。そして、切断された行セットは、それらのうち、システムの要求に最も合ったものを使用できます。SyncProvider
クラスおよび SyncResolver
インタフェースについては、「javax.sql.rowset.spi
パッケージの使用」を参照してください。
CachedRowSet
インタフェースの実装は、ほかの切断された行セットを実装する際の基盤として使えます。たとえば、WebRowSet
、FilteredRowSet
、JoinRowSet
の各インタフェースの実装はすべて、CachedRowSet
インタフェースの実装に基づいて作成されています。したがって、CachedRowSet
インタフェースに関する以下のセクションでは、このインタフェース内に定義されているメソッドとそれらの処理内容について、ある程度詳しく説明します。
切断された行セットのその他の実装に関するセクションでは、CachedRowSet
実装に含まれていない機能について説明します。
RowSet
オブジェクトは JavaBeans コンポーネントです。このため、開発者は通常、アプリケーション構築中にビジュアルな JavaBeans 開発ツールを使ってそれらのオブジェクトを作成します。また、RowSet
インタフェースを実装したクラスが提供する public のコンストラクタを使って、アプリケーション内で実行時にインスタンスを作成することも可能です。たとえば、CachedRowSet
インタフェースの標準実装には、public のデフォルトコンストラクタが定義されています。したがって、次のコード行を使えば、CachedRowSet
のインスタンスを作成できます。
CachedRowSet crset = new CachedRowSetImpl();新しく作成された
crset
は、一連のデータを格納するためのコンテナであり、BaseRowSet
オブジェクトのデフォルトのプロパティを含んでいます。この実装は、BaseRowSet
クラスを使用しています。このため、crset
は BaseRowSet
のメソッドを使って、それらのデフォルトプロパティに新しい値を設定したり、その他のプロパティに必要に応じて値を設定したりできます。たとえば、次のコードでは、1 行目で、crset
がデータ取得時に使用するコマンド文字列を設定し、2 行目で、エスケープ処理をオフにしています。
crset.setCommand("SELECT * FROM EMPLOYEES"); crset.setEscapeProcessing(false);
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");
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
インタフェースの適切な取得メソッドを使用することができます。
メソッド execute
と populate
には、類似点がいくつかあります。最大の類似点は、どちらのメソッドも行セット全体の内容を変更する、という点です。その結果、どちらのメソッドでも、リスナーへの通知処理、現在の行セット値を元の値に設定する処理、および行セットのメタデータ情報の更新処理が実行されます。
メソッド 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();次の例では、メソッド
execute
に Connection
オブジェクト con
が渡されています。CachedRowSet
オブジェクト crset2
は、con
を使ってコマンド文字列を実行します。したがって、新しい接続を内部的に確立する必要はありません。
CachedRowSet crset2 = new CachedRowSetImpl(); crset2.setCommand("SELECT NAME, SALARY FROM EMPLOYEES"); crset2.execute(con);
crset1
と crset2
はどちらも、コマンド文字列を実行し、その結果データを読み込み、それを自身に挿入する間は、データソースに接続されていました。メソッド execute
の実行が終了すると、どちらの場合もデータソースへの接続が閉じられます。
CachedRowSet
オブジェクトは、ほかのすべての行セットと同じく、ResultSet
インタフェースから継承した取得メソッドを使ってデータにアクセスします。CachedRowSet
オブジェクトはデフォルトでスクロール可能になるように実装されているため、ResultSet
のカーソル移動用メソッドを使用することもできます。たとえば、次のコードでは、CachedRowSet
オブジェクト crset
の最後の行にカーソルを移動したあと、その行の最初の列の String
値を取得しています。
crset.last(); String note = crset.getString(1);
CachedRowSet
オブジェクトは常に ResultSet.TYPE_SCROLL_INSENSITIVE
型であるように実装されています。したがって、CachedRowSet
オブジェクトは、スクロール可能であることに加え、外部による変更を反映しません。これは道理にかなっています。なぜなら、CachedRowSet
オブジェクトはほとんどの期間切断された状態にあり、切断されている間は、データの取得先である基となるデータソースに対して外部のユーザが行った変更を知る方法がないからです。
CachedRowSet
オブジェクトの内容全体を変更するには、メソッド execute
と populate
を使用します。一度に 1 つの行を変更するには、ResultSet
の更新メソッドとメソッド insertRow
、deleteRow
を使用します。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
RowSet
の実装では、行セットフレームワークが提供するデータ取得/更新機能をカスタマイズすることができます。これは、CachedRowSet
オブジェクトなどの切断された行セットに適用されます。というのも、切断された行セットでは、リーダとライターのサービスが必要となりますが、それらのサービスは SyncProvider
オブジェクトによってカプセル化されているからです。
CachedRowSet
インタフェースには、CachedRowSet
オブジェクトにリーダとライターの実装を設定するためのメソッド setSyncProvider
が定義されています。また、行セットの SyncProvider
オブジェクトを取得するためのメソッド getSyncProvider
も定義されています。行セットのリーダとライターは、完全にシステム内部で動作し、任意の数のタスクを実行しますが、それらのタスクをカスタマイズすれば、追加機能を提供できます。
RowSetReader
インタフェースに含まれる単一のメソッド readData
(public メソッド) は、さまざまな方法でカスタマイズ可能です。たとえば、JDBC ドライバを使ってデータベースから読み込む代わりに、ファイルその他の非 SQL データソースからデータを直接読み込むように、リーダを実装してもかまいません。そのようなリーダは、メソッド RowSet.insertRow
を使って新しい行を行セットに挿入できます。また、このメソッドは、リーダから呼び出された場合には行セット内の元の値を更新するように実装することもできます。
RowSetWriter
インタフェースに含まれる唯一のメソッド (public メソッド) writeData
は、変更されたデータを基となるデータソースに書き戻します。このメソッドも、さまざまな方法でカスタマイズできます。ライターはリーダと同じく、データソースとの接続を確立します。ただし、競合の有無をチェックするかどうかは、実装方法によって異なります。RowSetInternal
のメソッド getOriginal
と getOriginalRow
は、現在の変更前に存在していた値を返します。したがって、ライターは、それらの値をデータソースから読み込んだ値と比較することで、そのデータソースが変更されたかどうかを判断できます。競合が存在している場合にデータを書き込むかどうかを、ライターがどのように判断するかは、やはり実装方法によって異なります。
カスタマイズされたリーダやライターと連携するように設定された CachedRowSet
オブジェクトは、通常の JavaBeans コンポーネントとして提供できます。したがって、アプリケーションを作成する開発者は、リーダやライターのカスタマイズについて考慮せずにすみ、行セットをいかに効果的に使用するかという、より重要な側面に集中できるようになります。
CachedRowSet
インタフェースには、ほかにも多くのメソッドが定義されています。たとえば、2 つのバージョンのメソッド toCollection
が定義されています。行セット内のデータをコレクション内の要素として処理したほうが便利な場合があります。その場合、これらのメソッドは行セットのデータを Java のコレクションに変換します。CachedRowSet
には、行セットのコピーを作成するメソッドも用意されています。CachedRowSet.clone
と CachedRowSet.createCopy
は、指定された行セットとまったく同じコピーを作成します。なお、作成されたコピーは元の行セットから独立しています。これに対し、CachedRowSet.createShared
メソッドは、元の行セットと状態を共有する行セットを作成します。言い換えると、新しい行セットと元の行セットは、物理的に同一のメモリ領域 (元の値と現在の値) を共有します。状態を共有している行セットの一方で更新メソッドが呼び出された場合、その更新内容は他方にも反映されます。結果的に、createShared
メソッドを使えば、単一の行セットに対して複数のカーソルを作成できることになります。
javax.sql.rowset.spi
は、開発者が同期プロバイダを実装する際に使用する必要のある API を提供します。このパッケージに含まれているクラスとインタフェースは、次のとおりです。
SyncFactory
SyncFactoryException
SyncProvider
SyncProviderException
SyncResolver
XmlReader
XmlWriter
TransactionalWriter
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 つのリファレンス実装同期プロバイダ RIOptimisticProvider
と RIXmlProvider
に対するエントリが、最初から含まれています。SyncProvider
実装の 4 つ目の登録方法は、目的の実装を特定の JNDI コンテキストとして登録したあと、その JNDI コンテキストを SyncFactory
に登録するという方法です。その際、完全修飾プロバイダ名をメソッド SyncFactory.registerJNDIContext
に指定します。
RowSet
オブジェクトが特定の SyncProvider
実装を要求すると、SyncFactory
は、その実装を検索してインスタンスを作成し、それを RowSet
オブジェクトに返します。たとえば、次のコード行では、CachedRowSet
オブジェクト crset
が、com.supersoftware.providers.HAProvider
を要求しています。
crset.setSyncProvider("com.supersoftware.providers.HAProvider");要求されたプロバイダが使用可能ないずれの方法でも登録されていなかった場合、
SyncFactory
は SyncFactoryException
オブジェクトをスローします。
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
オブジェクトは、次の競合に移動し、競合がなくなるまで以上の処理を繰り返します。
CachedRowSet
インタフェースの実装は、接続が切断されており、ドライバがなくても動作可能です。また、特に thin クライアント上で分散アプリケーションを使ってデータを受け渡したり、結果セットをスクロール可能/更新可能にできるように設計されています。このほかにも、さまざまな目的のために、多数の RowSet
実装を設計することが可能です。
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);
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
オブジェクトへの変更は同期されません。
JoinRowSet
オブジェクトを使えば、プログラマは異なる 2 つの RowSet
オブジェクト内のデータを結合することができます。この機能は特に、関連データが複数のデータソースに格納されている場合に便利です。結合には任意の RowSet
実装を使用できますが、通常結合されるのは、2 つの CachedRowSet
オブジェクトです。すべての関連データを結合して単一の JoinRowSet
オブジェクトとして表現すれば、アプリケーションは、ほかの種類の RowSet
オブジェクトとまったく同様に、そのデータを処理できます。
次のコードは、2 つの CachedRowSet
オブジェクトを結合して単一の JoinRowSet
オブジェクトとして表現する方法を示したものです。このシナリオでは、テーブル EMPLOYEES
内のデータとテーブル BONUS_PLAN
内のデータを結合することで、各従業員の個人情報とボーナス計画情報のすべてを、単一の行セットとして提供しています。元の行セットはどちらも、最初の列に従業員識別番号を含んでいるため、両テーブル内のデータを結合する際にその最初の列が使用されています。結合後、JoinRowSet
オブジェクトの各行には、両方の行セットの列からなる、同一従業員 ID に関連付けられたデータが含まれています。
コードの 1 行目では、JoinRowSet
オブジェクト jrs
を作成しています。続いて、テーブル EMPLOYEES
内のすべての列のデータを新しい CachedRowSet
オブジェクト empl
に設定し、empl
の最初の列を一致列として設定し、その empl
を jrs
に追加しています。続いて同様に、テーブル BONUS_PLAN
内のすべての列のデータを CachedRowSet
オブジェクト bonus
に設定し、bonus
の最初の列を一致列として設定し、その bonus
を jrs
に追加しています。empl
と bonus
のどちらも、最初の列は 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
であってはなりません。