Oracle® Fusion Middleware Oracle WebLogic Server JDBC プログラマーズ ガイド 11g リリース 1 (10.3.1) B55535-01 |
|
![]() 戻る |
![]() 次へ |
以下の節では、WebLogic RowSet の特徴と使い方について説明します。
WebLogic Server には、JSR-114 の仕様に準拠した Java RowSet が実装されています。この仕様の詳細については、Sun の Web サイト (http://java.sun.com/products/jdbc/download.html
) を参照してください。WebLogic の RowSet 実装には、RowSet 仕様の拡張も含まれています。こうした拡張を使用すると、アプリケーションで RowSet をより有効に活用できます。
RowSet は Java ResultSet の拡張です。ResultSet と同様、RowSet も表形式のデータを保持する Java オブジェクトです。ただし、RowSet では ResultSet 機能に大幅な柔軟性が加えられていて、ResultSet の持つ制限の一部が緩和されたり削減されたりしています。
WebLogic Server の RowSet 実装には、以下のような種類とユーティリティがあります。
標準の RowSet :
WebLogic の RowSet 拡張 :
WebLogic Server の RowSet 実装は、ユーザが以下の手順で RowSet を扱うことを想定して設計されています。
RowSet を作成してコンフィグレーションします (クエリ、データベース接続、その他のプロパティを定義します)。
RowSet にデータを入力します (クエリ パラメータを指定してクエリを実行します)。
必要に応じて RowSet メタデータを指定します。
必要に応じて RowSet にフィルタまたはソーターを設定します。
RowSet 内のデータを操作します (挿入、更新、削除を行います)。
RowSet からデータベースへのデータ変更の同期を行います。
変更を同期した後には、アプリケーションの設計に応じて手順 2 または手順 3 から繰り返し処理を行えます。「トランザクション完了後の WebLogic RowSet の再利用」を参照してください。
WebLogic の RowSet 実装にはライフ サイクルのフレームワークがあり、これによって RowSet オブジェクトが異常な状態にならないようにしています。WebLogic Server では、上記の処理段階に応じて、内部的に RowSet に対しライフ サイクルのステージが設定されます。データが失われるリスクを減らすため、ライフ サイクルのステージに応じて RowSet に実施できる操作が制限されます。たとえば RowSet のステージが「Updating」(更新) の場合、RowSet に対して呼び出せるのは、update
XXX
()
メソッド (updateString()
、updateInt()
など) のみです。この制限は、updateRow()
を呼び出して更新のフェーズが完了するまで適用されます。
重要な注意事項を以下に示します。
保留中の変更がある場合、RowSet に対して再入力、フィルタ、ソートは行えない。データの変更が不測の事態によって失われることを防ぐため、RowSet のデータを変更してもその変更がデータベースとの間で同期されていない場合は RowSet に対してこれらの操作ができないようになっています。
カーソルが暗黙的に移動することはない。行から行へ明示的にカーソルを移動する必要があります。
RowSet のライフ サイクルのステージは内部的なプロセスである。ライフサイクルのステージにアクセスするためのパブリック API はありません。ライフサイクルのステージをユーザが設定することはできません。acceptChanges() または restoreOriginal() を呼び出すと RowSet のライフ サイクルのステージは終了し、再び処理を開始できます。
注意 : クライアントサイド アプリケーションで RowSet を使用する際は、サーバとクライアントの両方のCLASSPATH に、同一の JDBC ドライバ クラスを指定する必要があります。ドライバ クラスが一致していないと、java.rmi.UnmarshalException 例外が送出される場合があります。 |
RowSet の作成時からデータ変更がデータベースとの間で同期されるまでの RowSet のライフ サイクルのステージの実例については、コード リスト 6-1 のコメントを参照してください。
以下の節では、WebLogic Server における標準の CachedRowSet の使い方について説明します。
また、標準の CachedRowSet オブジェクトの WebLogic 拡張の使用方法については、「WLCachedRowSet」を参照してください。
CachedRowSet は接続されていない ResultSet
オブジェクトです。CachedRowSet のデータはメモリに格納されます。WebLogic Server 実装の CachedRowSet には以下の特性があります。
データの挿入、更新、削除に使用できる。
シリアライズ可能なので、シン クライアントや無線デバイスなどのさまざまなアプリケーション コンポーネントに渡すことができる。
トランザクション処理に対応しているため RowSet を再利用できる。「トランザクション完了後の WebLogic RowSet の再利用」を参照してください。
RowSet でのデータ変更をデータベースとの間で同期するためにオプティミスティックな同時実行性の制御が使用される。
SyncProvider 例外から得られる SyncResolver オブジェクトを使用して、RowSet でのデータ変更とデータベース間の衝突を解決する。「SyncResolver を使用した SyncProviderException の処理」を参照してください。
アプリケーションの設計時には、次のことを考慮するようにします。
RowSet のクエリ結果はすべてメモリに格納される
データの競合
CachedRowSet ではデータベースへの接続が保持されないので、すべてのクエリの結果は必ずメモリに格納されます。クエリの結果が非常に大きいと、メモリ不足エラーによりパフォーマンスが低下することがあります。大きなデータ セットの場合、ResultSet を使用する方が適していることもあります。ResultSet ではデータベースへの接続が保持されるため、クエリ結果の一部をメモリに保持してから必要に応じてデータベースに戻り、さらに行を取得できるためです。
CachedRowSet は、RowSet への入力から RowSet 内のデータ変更がデータベースとの間で同期されるまでの間に、別のプロセスで更新されることがあまりないようなデータに使用するのがもっとも適しています。この期間にデータベースが変更されるとデータの競合が発生します。データの競合を検出して処理する方法については、「SyncResolver を使用した SyncProviderException の処理」を参照してください。
コード リスト 6-1 に CachedRowSet の基本的なワークフローを示します。このコード リストでは、主要な各操作と対応する RowSet のライフ サイクルのステージについてコメントを使って説明します。このサンプル コードの後で、サンプルの主要な各セクションについてさらに詳細に説明します。
コード リスト 6-1 CachedRowSet のサンプル コード
import javax.sql.rowset.CachedRowSet; import weblogic.jdbc.rowset.RowSetFactory; public class CachedRowSetDemo { public static void main (String[] args) { // 「DESIGNING」(設計) ライフサイクル ステージ - RowSet を作成してプロパティを設定する try { // RowSetFactory インスタンスを作成する RowSetFactory rsfact = RowSetFactory.newInstance(); CachedRowSet rs = rsfact.newCachedRowSet(); // DataSource を介してデータベース アクセスを設定する rs.setDataSourceName(examples-dataSource-demoPool); // 他のオプションについては、「データベース接続のオプション」を参照 // クエリ コマンドを設定する rs.setCommand("SELECT ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, PHONE, EMAIL FROM PHYSICIAN WHERE ID>?"); // 「CONFIGURE QUERY」(クエリのコンフィグレーション) ライフサイクル操作 rs.setInt(1, 0); // 「POPULATING」(入力) ライフサイクル ステージ - RowSet に入力するためのコマンドを実行する rs.execute(); } // 「CONFIGURING METADATA」(メタデータのコンフィグレーション) - メタデータを入力してから設定する // これには KeyColumn を含む rs.setKeyColumns(new int[] { 1 }); while (rs.next ()) // 「NAVIGATING」(案内) ライフサイクル ステージ { System.out.println ("ID: " +rs.getInt (1)); System.out.println ("FIRST_NAME: " +rs.getString (2)); System.out.println ("MIDDLE_NAME: " +rs.getString (3)); System.out.println ("LAST_NAME: " +rs.getString (4)); System.out.println ("PHONE: " +rs.getString (5)); System.out.println ("EMAIL: " +rs.getString (6)); } } // データを操作する // RowSet の行を削除する try { // 「MANIPULATING」(操作) ライフサイクル ステージ - 行に移動する // (カーソルを手動で移動する) rs.last(); rs.deleteRow(); // データベースはまだ更新されていないことに注意 } // RowSet の行を更新する try { // 「MANIPULATING」(操作) ライフサイクル ステージ - 行に移動する // (カーソルを手動で移動する) rs.first(); // 「UPDATING」(更新) ライフサイクル ステージ - update() メソッドを呼び出す rs.updateString(4, "Francis"); // 「MANIPULATING」(操作) ライフサイクル ステージ - 更新を完了する rs.updateRow(); // データベースはまだ更新されていないことに注意 } // 「INSERTING」(挿入) ライフサイクル ステージ - RowSet に行を挿入する try { rs.moveToInsertRow(); rs.updateInt(1, 104); rs.updateString("FIRST_NAME", "Yuri"); rs.updateString("MIDDLE_NAME", "M"); rs.updateString("LAST_NAME", "Zhivago"); rs.updateString("PHONE", "1234567812"); rs.updateString("EMAIL", "Yuri@poet.com"); rs.insertRow(); // 「更新の完了」アクション // 「MANIPULATING」(操作) ライフサイクル ステージ - 行に移動する rs.moveToCurrentRow(); // データベースはまだ更新されていないことに注意 } // すべての変更 (削除、更新、挿入) をデータベースに送信する // 「DESIGNING」(設計) または「POPULATING」(入力) ライフサイクル ステージ - データベースとの間で変更を同期した後は // 他の環境設定に応じたライフサイクル ステージになる // 「トランザクション完了後の WebLogic RowSet の再利用」を参照 try { rs.acceptChanges(); rs.close(); } }
標準の RowSet を使用するには、次のクラスをインポートする必要があります。
javax.sql.rowset.CachedRowSet; weblogic.jdbc.rowset.RowSetFactory;
RowSet はファクトリ インタフェースから作成されます。WebLogic Server で RowSet を作成するには、主に以下の手順に従います。
RowSetFactory インスタンスを作成します。このインスタンスはアプリケーションで使用する RowSet オブジェクトを作成するためのファクトリとして機能します。RowSetFactory のデータベース接続のプロパティを指定すると、同じデータベース接続を使用する複数の RowSet を少ないコード行で作成できます。
RowSetFactory rsfact = RowSetFactory.newInstance();
WLCachedRowSet を作成して javax.sql.rowset.CachedRowSet
オブジェクト型にキャストします。デフォルトでは WebLogic newCachedRowSet()
RowSetFactory メソッドで WLCachedRowSet
オブジェクトが作成されます。このオブジェクトをそのまま使用することもできますが、標準の CachedRowSet
オブジェクトを使用する場合は、WLCachedRowSet オブジェクトを CachedRowSet オブジェクトにキャストできます。
CachedRowSet rs = rsfact.newCachedRowSet();
RowSet には、同時方式の種類、データ ソースの名前、トランザクション アイソレーション レベルなど数多くのプロパティがあります。これらのプロパティを設定して RowSet の動作を決定できます。RowSet の特定の用途に応じて必要なプロパティのみを設定する必要があります。利用可能なプロパティについては、http://java.sun.com/javase/6/docs/api/javax/sql/rowset/BaseRowSet.html
の javax.sql.rowset.BaseRowSet
クラスの Javadoc を参照してください。
ほとんどのアプリケーションでは、データベースからのデータを RowSet に入力します。RowSet とデータベースとの接続は、以下のいずれかの方法で設定します。
データ ソースを使用して自動的に接続する - setDataSourceName()
メソッドを使用して、JDBC データ ソースの JNDI 名を指定できます。execute()
および acceptChanges()
を呼び出すと、RowSet でデータ ソースからのデータベース接続が取得され、それが使用されて、データ ソースの接続のプールに戻されます。これは、推奨される方法です。
rs.setDataSourceName(examples-dataSource-demoPool);
データベース接続を手動で取得する - RowSet で必要になる前にアプリケーションでデータベース接続を取得してから、接続オブジェクトを execute()
メソッドおよび acceptChanges()
メソッドのパラメータとして渡します。必要に応じて接続を閉じる必要もあります。
// DataSource をルックアップして接続を取得する ctx = new InitialContext(ht); javax.sql.DataSource ds = (javax.sql.DataSource) ctx.lookup ("myDS"); conn = ds.getConnection(); // 接続を RowSet に渡す rs.execute(conn);
JDBC データ ソースの詳細については、「DataSource オブジェクトからのデータベース接続の取得」を参照してください。
JDBC ドライバをロードして直接的に接続する - JDBC ドライバをロードして適切なプロパティを設定すると、RowSet では execute()
および acceptChanges()
を呼び出したときにデータベース接続が作成されます。RowSet では接続を使用し終えるとすぐにその接続が閉じられます。RowSet では execute()
メソッド呼び出しと acceptChanges()
メソッド呼び出しの間、接続は保持されません。
Class.forName("com.pointbase.jdbc.jdbcUniversalDriver"); rs.setUrl("jdbc:pointbase:server://localhost/demo"); rs.setUsername("examples"); rs.setPassword("examples"); rs.execute();
RowSetFactory の接続のプロパティを設定する - RowSetFactory のデータベース接続のプロパティを設定すると、RowSetFactory から作成されたすべての RowSet でその接続のプロパティが継承されます。データ ソースをルックアップしてから setDataSource()
メソッドで RowSetFactory のデータ ソースのプロパティを設定するのが、推奨される方法です。
// DataSource をルックアップして接続を取得する ctx = new InitialContext(ht); javax.sql.DataSource ds = (javax.sql.DataSource) ctx.lookup ("myDS"); // RowSetFactory にデータ ソースのプロパティを設定する rsfact.setDataSource(ds);
RowSet への「Populating」(入力) とは、RowSet にデータの行を格納する作業のことをいいます。通常、このデータの取得元はリレーショナル データベースです。データベースからのデータは、以下のいずれかの方法で RowSet に入力できます。
setCommand()
メソッドで SQL コマンドを設定してから、execute()
メソッドでそのコマンドを実行する。
rs.setCommand("SELECT ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, PHONE, EMAIL FROM PHYSICIAN"); rs.execute();
既存の結果セットから populate()
メソッドを使用して入力する。
rs.populate(resultSet);
注意 : ResultSet.TYPE_FORWARD_ONLY の結果セットを使用している場合、以下の条件で RowSet に入力しようとすると SQLException が送出されます。
|
RowSet のデータ変更をデータベースとの間で同期するために、RowSet にメタデータを設定することが必要な場合もあります。詳細については、「RowSet に対するデータベース更新用メタデータの設定」を参照してください。
キャッシュされた RowSet に一連のデータ行を入力した後には、結果セットのデータを扱うのとほぼ同じ方法でそのキャッシュ済みのデータを取り扱えます。ただし、データベースに対して変更を行う前には、acceptChanges()
を明示的に呼び出す必要があります。
注意 : RowSet のカラム名やテーブル名に区切り識別子は使用できません。SQL 文の中では、区切り識別子は二重引用符で囲む必要があります。区切り識別子には、SQL 予約語 (USER や DATE など) になっているものや、識別子ではない名前になっているものがあります。有効な識別子は必ず文字で始まり、文字、数字、およびアンダースコアのみを使用できます。 |
RowSet からデータを取得するには、結果セットの場合と同様に get
XXX
メソッドを使用します。次に例を示します。
while (rs.next ()) { int id = rs.getInt (1); String fname = rs.getString ("FIRST_NAME"); String mname = rs.getString ("MIDDLE_NAME"); String lname = rs.getString ("LAST_NAME")); }
通常、データの更新は以下の手順で行います。
行または挿入行に移動します。
update
XXX
メソッドで行を変更します。
updateRow()
または insertRow()
で操作を完了します。
操作を完了しても変更はデータベースとの間で同期されないことに注意します。変更は RowSet に対してのみ行われます。acceptChanges()
を呼び出して、明示的に変更を同期させる必要があります。詳細については、下記の「RowSet の変更をデータベースとの間で同期する」を参照してください。
RowSet を操作する際、WebLogic Server では内部的に各操作の後で RowSet にライフ サイクルのステージが設定され、その後は現在のライフ サイクルのステージに基づいて RowSet に対する以降の操作が制限されます。update メソッド群で行の変更を開始したら、必ず updateRow()
または insertRow()
で操作を完了します。完了してはじめて、別の行の操作 (別の行へのカーソルの移動を含む) ができるようになります。RowSet のライフ サイクルのステージと、各ステージで許可される操作の詳細については、「RowSet を使用したプログラミング」を参照してください。
行を更新するには、更新する行にカーソルを移動して、その行の個別のカラムに対して update
XXX
メソッドを呼び出してから、updateRow()
を呼び出して操作を完了します。次に例を示します。
rs.first(); rs.updateString(4, "Francis"); rs.updateRow();
注意 : 複数のテーブルの同名のカラムを更新する場合、カラムの参照には Update 文でカラムのインデックス番号を使用する必要があります。 |
行を挿入するには、新規の挿入行にカーソルを移動してその行のカラムの値を更新してから、insertRow()
を呼び出して RowSet に行を追加します。次に例を示します。
rs.moveToInsertRow(); rs.updateInt(1, 104); rs.updateString("FIRST_NAME", "Yuri"); rs.updateString("MIDDLE_NAME", "M"); rs.updateString("LAST_NAME", "Zhivago"); rs.updateString("PHONE", "1234567812"); rs.updateString("EMAIL", "Yuri@poet.com"); rs.insertRow(); rs.moveToCurrentRow();
行を挿入した後には、明示的にカーソルを移動する必要があります。カーソルが暗黙的に移動することはありません。
RowSet の個別の行を変更した後には、acceptChanges()
を呼び出して変更をデータベースに伝播します。次に例を示します。
rs.acceptChanges();
acceptChanges()
を呼び出すと、RowSet では、すでに RowSet で使用されているデータベース接続情報を使用するか (「データベース接続のオプション」を参照)、acceptChanges(connection)
メソッドで渡された接続オブジェクトを使用して、データベースに接続します。1 つまたは複数の行を変更した後に acceptChanges()
を呼び出せます。RowSet に対してすべての変更を行った後に acceptChanges()
を呼び出すと、RowSet からデータベースへの接続が 1 度だけになるのでより効率的です。
WebLogic Server で RowSet を使用する場合、データベースに対する書き込みと読み取りには weblogic.jdbc.rowset.WLSyncProvider
オブジェクトが内部的に使用されます。WLSyncProvider ではデータベースの変更にオプティミスティックな同時実行方式が使用されます。つまり、RowSet への入力時から RowSet のデータの変更がデータベースに伝播されるまでの間に、データベースのデータが別のプロセスによって変更されないと仮定されます。データベースに変更を書き込む前には、WLSyncProvider によってデータベースのデータと RowSet の元の値 (RowSet の作成時または最後の同期の際に RowSet に読み込まれた値) が比較されます。データベースの値になんらかの変更があった場合には javax.sql.rowset.spi.SyncProviderException
が送出され、データベースにはどの変更も書き込まれません。アプリケーションでこの例外を捕捉して、処理方法を決定できます。詳細については、「SyncResolver を使用した SyncProviderException の処理」を参照してください。
WLCachedRowSet
インタフェースは CachedRowSet
インタフェースの拡張で、このインタフェースでオプティミスティックな同時実行性ポリシーを選択できます。詳細については、「オプティミスティックな同時実行性ポリシー」を参照してください。
変更をデータベースに伝播した後には、アプリケーションの環境に応じて RowSet のライフ サイクルのステージが「Designing」(設計) または「Populating」(入力) に変わります。「Designing」(設計) ステージの場合、再び使用する前に RowSet に再入力する必要があります。「Populating」(入力) ステージの場合、現在のデータを持つ RowSet を使用できます。詳細については、「トランザクション完了後の WebLogic RowSet の再利用」を参照してください。
RowSet を再び使用する予定がない場合は、close()
メソッドで閉じる必要があります。次に例を示します。
rs.close();
SQL クエリで RowSet に入力する場合、WebLogic の RowSet 実装では ResultSetMetaData
インタフェースを使用して、自動的に RowSet 内のデータのテーブル名とカラム名が認識されます。ほとんどの場合、RowSet が変更をデータベースに書き戻すために必要な SQL を生成するには、この情報で十分です。ただし一部の JDBC ドライバでは、このクエリで返される行にテーブルおよびカラムのメタデータが含まれません。RowSet のデータ変更をデータベースとの間で同期しようとすると、次のエラーが表示されます。
java.sql.SQLException: Unable to determine the table name for column:
column_name
. Please ensure that you've called WLRowSetMetaData.setTableName to
set a table name for this column.
テーブル名がない場合、RowSet は読み込み操作にのみ使用できます。テーブル名をプログラム的に指定しない限り、RowSet では更新を発行できません。また、setKeyColumns()
メソッドによる主キー カラムの設定が必要な場合もあります。次に例を示します。
rs.setTableName(PHYSICIAN); rs.setKeyColumns(new int[] { 1 });
詳細については、javax.sql.rowset.CachedRowSet
インタフェースの説明を参照してください。
以下の節では、RowSet に対する適切なメタデータの取得または設定に使用できる WebLogic RowSet 拡張について説明します。
SQL クエリで RowSet に入力する場合、通常は execute()
メソッドを使用してクエリを実行し、データを読み込みます。WLCachedRowSet
実装には、関連付けられたテーブルのメタデータも識別するために execute
メソッドを拡張した executeAndGuessTableName
メソッドおよび executeAndGuessTableNameAndPrimaryKeys
メソッドが用意されています。
executeAndGuessTableName
メソッドでは、関連付けられた SQL を解析して、すべてのカラムのテーブル名を、SQL キーワード FROM
に続く最初の語として設定します。
executeAndGuessTableNameAndPrimaryKeys
メソッドでは、SQL コマンドを解析してテーブル名を読み取ってから、java.sql.DatabaseMetaData
を使用して、テーブルの主キーを識別します。
注意 : これらのメソッドは DBMS または JDBC ドライバのサポートに依存しています。すべての DBMS、すべての JDBC ドライバで機能するとは限りません。 |
WLRowSetMetaData
インタフェースを使用してテーブルおよび主キー情報を手動で設定することもできます。
WLRowSetMetaData metaData = (WLRowSetMetaData) rowSet.getMetaData(); // すべてのカラムに 1 つのテーブル名を設定する metaData.setTableName("employees");
または
metaData.setTableName("e_id", "employees"); metaData.setTableName("e_name", "employees");
WLRowSetMetaData
インタフェースを使用して主キー カラムを指定することもできます。
metaData.setPrimaryKeyColumn("e_id", true);
詳細については、weblogic.jdbc.rowset.WLRowSetMetaData の Javadoc を参照してください。
WLRowSetMetaData
インタフェースには、更新または削除するテーブルのみを指定するための setWriteTableName
メソッドがあります。このメソッドは通常、複数のテーブルの結合が入力されている RowSet において、1 つのテーブルのみを更新する場合に使用します。書き込みテーブルに属さないカラムは読み込み専用としてマークされます。
たとえば、RowSet に「注文」と「顧客」の結合が含まれるとします。書き込みテーブルを「注文」に設定します。deleteRow を呼び出すと、「注文」の行は削除されますが、「顧客」の行は削除されません。
注意 : JSR-114 は、WebLogic CachedRowSetMetaData.setWriteTableName メソッドと同じ機能を備えた CachedRowSet.setTableName (http://java.sun.com/javase/6/docs/api/javax/sql/rowset/CachedRowSet.html#setTableName(java.lang.String) を参照) を提供します。どちらのメソッドを呼び出しても、書き込みテーブルに属さないカラムが読み取り専用のものとしてマークされます。WebLogic ではまた、カラムがどのテーブルに属するかのマッピングに使用される CachedRowSetMetaData.setTableName メソッドが用意されています。setTableName を使用して書き込みテーブルを設定する際には、アプリケーションに適した API を使用してメソッドを実装するように注意してください。 |
ほとんどのデータベースや JDBC アプリケーションではトランザクションを使用します。RowSet は JTA トランザクションを含むトランザクションをサポートしています。一般的な使用例は次のとおりです。トランザクション 1 で RowSet を取得します。トランザクション 1 がコミットされます。基底のデータに対して、データベースやアプリケーション サーバによるロックはありません。RowSet はメモリ内にデータを保持します。データを変更したり、ネットワーク経由でクライアントに提供したりできます。アプリケーションはデータベースに対する変更をコミットする場合、トランザクション 2 を開始して、RowSet の acceptChanges
メソッドを呼び出します。その後トランザクション 2 がコミットされます。
EJB コンテナと UserTransaction インタフェースは JTA トランザクション マネージャを使用してトランザクションを開始します。RowSet の操作はこのトランザクションに参加することができます。JTA トランザクションに参加するには、RowSet はトランザクション対応の DataSource (TxDataSource) を使用する必要があります。DataSource は WebLogic Server コンソールでコンフィグレーションできます。
acceptChanges
でオプティミスティックな衝突や他の例外が発生した場合、RowSet はグローバル JTA トランザクションを中止します。アプリケーションは通常データを再び読み込んで、新しいトランザクションで更新を再び処理します。
JTA グローバル トランザクションを使用しない場合、RowSet はローカル トランザクションを使用します。最初に接続に対する setAutoCommit(false)
を呼び出します。次に SQL 文をすべて発行して、最後に connection.commit()
を呼び出します。これでローカル トランザクションをコミットしようとします。EJB または JMS コンテナによって開始された JTA トランザクションと統合する場合は、このメソッドは使用しないでください。
acceptChanges
でオプティミスティックな衝突や他の例外が発生した場合、RowSet はローカル トランザクションをロールバックします。この場合、acceptChanges
で発行された SQL は、いずれもデータベースにコミットされません。
この節では、失敗したローカル トランザクションにおける RowSet の動作について説明します。この動作は、接続オブジェクトの種類によって異なります。
この状況では、接続オブジェクトは RowSet によって作成されたものではなく、connection.commit
を呼び出すことによってローカル トランザクションを開始します。トランザクションが失敗するか、または接続が connection.rollback
を呼び出すと、データはデータベースからロールバックされますが、RowSet においてはロールバックされません。作業を進める前に、次のいずれかを行う必要があります。
rowset.refresh
を呼び出して、データベースからのデータで RowSet を更新する。
現在のデータで新しい RowSet を作成する。
多くの場合、RowSet の変更をデータベースとの間で同期した後に、その RowSet を現在入力されているデータのまま使用することが必要になるでしょう。そうすることでデータベースとの往復回数が減るため、アプリケーションのパフォーマンスを向上させることができます。ただし、RowSet とそのデータを再利用する場合、そのデータにさらに変更を加える前に、その RowSet が参加するトランザクションが必ずすべて完了している必要があります。
ローカル トランザクションで RowSet を使用していて、RowSet データの変更をデータベースとの間で同期する前に、接続オブジェクトに autocommit=true
と設定されている場合、データの同期後に現在の値のまま RowSet を再利用できます。これは、autocommit の設定によってローカル トランザクションがすぐに強制的に完了するためです。RowSet の変更前には、ローカル トランザクションが必ず完了します。
以下のいずれかの状況で RowSet を使用している場合、すべてのトランザクションが必ず自動的に完了するとは限りません。
グローバル トランザクションで使用している
ローカル トランザクションで、autocommit=false
と設定された接続オブジェクトを使用してデータの変更をデータベースと同期している
これらのいずれかの場合、現在のデータで RowSet を再利用する前に、acceptChanges()
を呼び出してデータベースとの間で変更を同期した後で、tx.commit()
または java.sql.Connection.commit()
の代わりに javax.sql.rowset.CachedRowSet.commit()
を呼び出してトランザクションをコミットします。CachedRowSet.commit()
メソッドには Connection.commit()
メソッドがラップされています。これを使用すると必ずトランザクションが完了され、その後に RowSet を変更できるようになります。
以下の節では、WebLogic Server における標準の FilteredRowSet の使い方について説明します。
FilteredRowSet を使用するとキャッシュされた行のサブセットを取り扱え、データベースに接続されていない状態で行のサブセットを変更できます。フィルタ処理された RowSet とは、分かりやすく言えば、キャッシュされている RowSet の中で、特定の行のみを閲覧、移動、操作できるものをいいます。FilteredRowSet には以下のような特性があります。
利用できる行は、アプリケーションで指定した javax.sql.rowset.Predicate
オブジェクトで決定され、setFilter()
メソッドで指定される。
Predicate オブジェクトには javax.sql.rowset.Predicate
インタフェースを実装する必要がある。Predicate インタフェースには public boolean evaluate(RowSet rs)
メソッドがあり、このメソッドで RowSet の各行を評価します。
このメソッドから true
が返された場合、その行は利用可能で閲覧することもできる。
このメソッドから false
が返された場合、その行は利用も閲覧もできない。
詳細については「FilteredRowSet に対するフィルタの設定」を参照してください。
WebLogic Server には weblogic.jdbc.rowset.SQLPredicate
クラスという javax.sql.rowset.Predicate
インタフェースの実装が用意されている。このクラスを使用すると、SQL のような WHERE 句の構文を使用して FilteredRowSet のフィルタを定義できます。「SQLPredicate、SQL 方式の RowSet フィルタ」を参照してください。
FilteredRowSet の WebLogic 実装では現在、FilteredRowSet にフィルタを 2 回設定すると古いフィルタが新しいフィルタで置き換えられます。JSR-114 にはこの点についてはっきりとした規定がありません。Oracle では上記のような動作を setFilter メソッドを実装する適切な方式と判断しました。Sun の参照実装は、これと同じようには動作しません。Sun の実装では RowSet のフィルタされた行がさらにフィルタされます。この場合 2 回目のフィルタを変更して必要なすべての条件をフィルタするようにすると、同じ効果を得られます。
RowSet フィルタを設定または変更する前に RowSet に保留中の変更がある場合、その変更を受け入れる (acceptChanges()
を呼び出す) か、RowSet のデータを変更前の状態に戻す (restoreOriginal()
を呼び出す) 必要があります。WebLogic Server では行われる可能性のある変更を示せるように RowSet 内の移動が認められており、ユーザは RowSet フィルタを変更する前に上記のどちらかのメソッドを呼び出す必要があります。acceptChanges()
ではデータベースへのアクセスが行われ、restoreOriginal()
では行われません。
以下のサンプルに、キャッシュされた RowSet を作成してから WebLogic Server SQLPredicate を使用してフィルタを適用および変更する方法について示します。
コード リスト 6-2 FilteredRowSet のサンプル コード
import javax.sql.rowset.FilteredRowSet; import weblogic.jdbc.rowset.RowSetFactory; import weblogic.jdbc.rowset.SQLPredicate; public class FilteredRowSetDemo { public static void main (String[] args) { // 「DESIGNING」(設計) ライフサイクル ステージ - RowSet を作成してプロパティを設定する try { // RowSetFactory インスタンスを作成して、このファクトリから // WLCachedRowSet を作成し FilteredRowSet にキャストする RowSetFactory rsfact = RowSetFactory.newInstance(); FilteredRowSet rs = rsfact.newCachedRowSet(); // DataSource を介してデータベース アクセスを設定する // 他のオプションについては、「データベース接続のオプション」を参照 rs.setDataSourceName(examples-dataSource-demoPool); rs.setCommand("SELECT ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, PHONE, EMAIL FROM PHYSICIAN WHERE ID>?"); // 「CONFIGURE QUERY」(クエリのコンフィグレーション) ライフサイクル操作 - クエリ パラメータに値を設定する rs.setInt(1, 0); // 「POPULATING」(入力) ライフサイクル ステージ - RowSet に入力するためのコマンドを実行する rs.execute(); } // 「CONFIGURING METADATA」(メタデータのコンフィグレーション) - 初めに入力してから、KeyColumn を含むメタデータを設定する rs.setKeyColumns(new int[] { 1 }); while (rs.next ()) // 「移動」操作により RowSet が「MANIPULATING」(操作) ライフサイクル ステージになる { System.out.println ("ID: " +rs.getInt (1)); System.out.println ("FIRST_NAME: " +rs.getString (2)); System.out.println ("MIDDLE_NAME: " +rs.getString (3)); System.out.println ("LAST_NAME: " +rs.getString (4)); System.out.println ("PHONE: " +rs.getString (5)); System.out.println ("EMAIL: " +rs.getString (6)); } } // RowSet を「DESIGNING」(設計) または「POPULATING」(入力) ステージにするために // 変更を受け入れるか restoreOriginal を呼び出す必要がある // 移動後に RowSet は「MANIPULATING」(操作) ステージになり // このライフサイクル ステージではプロパティは変更できない rs.restoreOriginal(); // フ ィ ル タ を 設 定 // SQLPredicate クラスを使用して SQLPredicate オブジェクトを作成してから // そのオブジェクトを setFilter メソッドに渡して RowSet をフィルタ処理する SQLPredicate filter = new SQLPredicate("ID >= 103"); rs.setFilter(filter); System.out.println("Filtered data: "); while (rs.next ()) { System.out.println ("ID: " +rs.getInt (1)); System.out.println ("FIRST_NAME: " +rs.getString (2)); System.out.println ("MIDDLE_NAME: " +rs.getString (3)); System.out.println ("LAST_NAME: " +rs.getString (4)); System.out.println ("PHONE: " +rs.getString (5)); System.out.println ("EMAIL: " +rs.getString (6)); System.out.println (" "); } // RowSet を「DESIGNING」(設計) または「POPULATING」(入力) ライフサイクル ステージにするために // 変更を受け入れるか restoreOriginal を呼び出す必要がある // 移動後に RowSet は「MANIPULATING」(操作) ステージになり // このライフサイクル ステージではプロパティは変更できない rs.restoreOriginal(); // フ ィ ル タ を 変 更 す る SQLPredicate filter2 = new SQLPredicate("ID <= 103"); rs.setFilter(filter2); System.out.println("Filtered data: "); while (rs.next ()) { System.out.println ("ID: " +rs.getInt (1)); System.out.println ("FIRST_NAME: " +rs.getString (2)); System.out.println ("MIDDLE_NAME: " +rs.getString (3)); System.out.println ("LAST_NAME: " +rs.getString (4)); System.out.println ("PHONE: " +rs.getString (5)); System.out.println ("EMAIL: " +rs.getString (6)); System.out.println (" "); } // RowSet を「DESIGNING」(設計) または「POPULATING」(入力) ライフサイクル ステージにするために // 変更を受け入れるか restoreOriginal を呼び出す必要がある // 移動後に RowSet は「MANIPULATING」(操作) ステージになり // このライフサイクル ステージではプロパティは変更できない rs.restoreOriginal(); // フ ィ ル タ を 削 除 す る rs.setFilter(null); while (rs.next ()) { System.out.println ("ID: " +rs.getInt (1)); System.out.println ("FIRST_NAME: " +rs.getString (2)); System.out.println ("MIDDLE_NAME: " +rs.getString (3)); System.out.println ("LAST_NAME: " +rs.getString (4)); System.out.println ("PHONE: " +rs.getString (5)); System.out.println ("EMAIL: " +rs.getString (6)); System.out.println (" "); } rs.close(); } }
標準の FilteredRowSet を使用するには、次のクラスをインポートする必要があります。
javax.sql.rowset.FilteredRowSet; weblogic.jdbc.rowset.RowSetFactory;
上記のサンプル コードでは、フィルタの作成に weblogic.jdbc.rowset.SQLPredicate
クラスも使用されています。アプリケーションでは、weblogic.jdbc.rowset.SQLPredicate
クラスを使用することも、独自のフィルタ クラスを作成することも可能です。詳細については「FilteredRowSet に対するフィルタの設定」を参照してください。
RowSet はファクトリ インタフェースから作成されます。WebLogic Server で FilteredRowSet を作成するには、主に以下の手順に従います。
RowSetFactory インスタンスを作成します。このインスタンスはアプリケーションで使用する RowSet オブジェクトを作成するためのファクトリとして機能します。次に例を示します。
RowSetFactory rsfact = RowSetFactory.newInstance();
WLCachedRowSet を作成して javax.sql.rowset.FilteredRowSet
オブジェクト型にキャストします。デフォルトでは WebLogic newCachedRowSet()
RowSetFactory メソッドで WLCachedRowSet
オブジェクトが作成されます。このオブジェクトをそのまま使用することもできますが、標準の FilteredRowSet
オブジェクトを使用する場合は、WLCachedRowSet オブジェクトを FilteredRowSet オブジェクトにキャストできます。次に例を示します。
FilteredRowSet rs = rsfact.newCachedRowSet();
FilteredRowSet のプロパティのオプションは CachedRowSet のオプションと同じです。「CachedRowSet のプロパティの設定」を参照してください。
FilteredRowSet のデータベース接続のオプションは CachedRowSet のオプションと同じです。「データベース接続のオプション」を参照してください。
FilteredRowSet のデータ入力のオプションは CachedRowSet のオプションと同じです。「CachedRowSet への入力」を参照してください。
RowSet のデータ変更をデータベースとの間で同期するために、RowSet にメタデータを設定することが必要な場合もあります。詳細については、「RowSet に対するデータベース更新用メタデータの設定」を参照してください。
FilteredRowSet の行をフィルタするには、setFilter
メソッドを呼び出して、Predicate (フィルタ) オブジェクトをこのメソッドのパラメータとして渡します。Predicate オブジェクトは javax.sql.rowset.Predicate
インタフェースを実装するクラスのインスタンスです。FilteredRowSet の WebLogic 実装では、独自のフィルタを定義することも、weblogic.jdbc.rowset.SQLPredicate
クラスのインスタンスを使用することもできます。
FilteredRowSet 用のフィルタを定義する場合、主に以下の手順に従います。
使用するフィルタ動作 (表示する行を特定のカラムの値を持つ行に制限するなど) を行う javax.sql.rowset.Predicate
インタフェースを実装するクラスを定義します。たとえば、ID カラムの値の範囲に基づいて表示する行を制限する場合、ID カラムの値をフィルタするロジックを含むクラスを定義します。
クラスのインスタンス (フィルタ) を作成して、使用するフィルタ条件を指定します。たとえば、ID カラムの値が 100 から 199 までの行のみを表示するといった指定が可能です。
rowset.setFilter()
を呼び出して、クラスをこのメソッドのパラメータとして渡します。
コード リスト 6-3 に、javax.sql.rowset.Predicate
インタフェースを実装するクラスのサンプルを示します。このサンプルでは、カラムの値の大文字と小文字を区別しないバージョンを評価するフィルタを作成することができるクラスが示されます。コード リスト 6-4 に、フィルタ条件を判断するクラスのインスタンスを作成してから、FilteredRowSet 用のフィルタとしてフィルタ オブジェクトを設定するコードを示します。
コード リスト 6-3 javax.sql.rowset.Predicate を実装するフィルタ クラス
package examples.jdbc.rowsets; import javax.sql.rowset.Predicate; import javax.sql.rowset.CachedRowSet; import javax.sql.RowSet; import java.sql.SQLException; public class SearchPredicate implements Predicate, java.io.Serializable { private boolean DEBUG = false; private String col = null; private String criteria = null; // カラムと値の大文字と小文字を区別しない比較を作成するコンストラクタ public SearchPredicate(String col, String criteria) { this.col = col; this.criteria = criteria; } public boolean evaluate(RowSet rs) { CachedRowSet crs = (CachedRowSet)rs; boolean bool = false; try { debug("evaluate(): "+crs.getString(col).toUpperCase()+" contains "+ criteria.toUpperCase()+" = "+ crs.getString(col).toUpperCase().contains(criteria.toUpperCase())); if (crs.getString(col).toUpperCase().contains(criteria.toUpperCase())) bool = true; } catch(Throwable t) { t.printStackTrace(); throw new RuntimeException(t.getMessage()); } return bool; } public boolean evaluate(Object o, String s) throws SQLException { throw new SQLException("String evaluation is not supported."); } public boolean evaluate(Object o, int i) throws SQLException { throw new SQLException("Int evaluation is not supported."); } }
Weblogic Server には javax.sql.rowset.Predicate
インタフェースを実装する weblogic.jdbc.rowset.SQLPredicate
クラスが用意されています。SQLPredicate
クラスを使用すると、SQL のような WHERE 句の構文を使用するフィルタを定義して RowSet の行をフィルタできます。次に例を示します。
SQLPredicate filter = new SQLPredicate("ID >= 103"); rs.setFilter(filter);
詳細については、「SQLPredicate、SQL 方式の RowSet フィルタ」を参照してください。
FilteredRowSet のデータの操作は CachedRowSet のデータの操作とほぼ同様です。ただし、行を挿入したり更新したりする場合には、必ずフィルタ条件の範囲内で変更を行って、操作した行が表示される行の集合に残るようにします。たとえば、RowSet のフィルタで ID カラムの値が 105 より小さい行のみを表示できる場合、ID カラムの値が 106 の行を挿入しようとしたり、ID の値を 106 に更新しようとしたりすると、操作は失敗して SQLException が送出されます。
データ操作の詳細については、「CachedRowSet のデータの操作」を参照してください。
WebRowSet は、RowSet を XML フォーマットで読み書きできるキャッシュされた RowSet です。WebRowSet には以下のような特性があります。
readXml(java.io.InputStream iStream)
メソッドを使用して XML ソースから RowSet に入力する。
writeXml(java.io.OutputStream oStream)
メソッドを使用してデータおよびメタデータを XML で書き込み、他のアプリケーション コンポーネントで使用できるように、またはリモート クライアントに送信できるようにする。
RowSet の入力や RowSet からの書き込みに使用される XML コードは、標準 WebRowSet XML スキーマ定義に準拠する。この定義は http://java.sun.com/xml/ns/jdbc/webrowset.xsd
で入手できます。
詳細については、Sun の Web サイト (http://java.sun.com/products/jdbc/download.html
) および javax.sql.rowset.WebRowSet
インタフェースの Javadoc (http://java.sun.com/javase/6/docs/api/javax/sql/rowset/WebRowSet.html
) を参照してください。
注意 : WebLogic Server では RowSet について 2 つのスキーマがサポートされています。1 つは標準 WebRowSet 用、もう 1 つは WLCachedRowSet 用です。これは、JSR-114 の最終仕様より前に実装されたものです。 |
WebLogic の WebRowSet 実装では 2 つの XML スキーマ (および API) がサポートされる。1 つは標準の WebRowSet 仕様で (http://java.sun.com/xml/ns/jdbc/webrowset.xsd
で入手可能)、もう 1 つは WLCachedRowSet 用です。これは、JSR-114 の最終仕様より前に実装されたものです。
WebLogic Server の RowSet のみを使用している場合、どちらのスキーマを使用することもできる。専用のスキーマには、以下のような利点があります。
より多くの種類の要素がある。
Oracle Workshop for WebLogic Platform の RowSet で使用される。
他の RowSet の実装と対話するには、標準のスキーマを使用する必要がある。
JoinRowSet とは、SQL JOIN によって単一の RowSet に結合された、多数の接続されていない RowSet オブジェクトです。JoinRowSet には以下のような特性があります。
JoinRowSet に追加される各 RowSet には、JoinRowSet への RowSet の追加に使用される addRowSet メソッドで指定された「一致する」カラムがあることが必要。次に例を示します。
addRowSet(javax.sql.RowSet[] rowset,java.lang.String[] columnName);
setJoinType メソッドを使用して、結合タイプを設定できる。以下の結合タイプがサポートされています。
CROSS_JOIN FULL_JOIN INNER_JOIN LEFT_OUTER_JOIN RIGHT_OUTER_JOIN
これらを使用すると、データベースに接続されていなくてもデータを結合できる。
JoinRowSets は読み取り専用。JoinRowSet はデータベースのデータを更新するためには使用できません。
JoinRowSet の一致するカラムは、Number、Boolean、Date、および String の 4 つのデータ型に制限されている。表 6-1 に、JoinRowSet の一致するカラムで使用できるデータ型の詳細について示します。
表 6-1 一致するカラムで使用できるデータ型
結合の左側のデータ型 | 結合の右側で使用できるデータ型 |
---|---|
Number |
Number String |
Boolean |
Boolean String |
Date |
Date String |
String |
String Number Boolean Date |
JoinRowSet の詳細については、javax.sql.rowset.Joinable
(http://java.sun.com/javase/6/docs/api/javax/sql/rowset/Joinable.html
) および JoinRowSet
インタフェース (http://java.sun.com/javase/6/docs/api/javax/sql/rowset/JoinRowSet.html
) の Javadoc を参照してください。
JDBCRowSet は ResultSet オブジェクトのラッパーで、これによって結果セットを JavaBean コンポーネントとして使用できるようになります。JDBCRowSet は、接続されている RowSet です。これ以外の種類の RowSet はすべて接続されていない RowSet です。
詳細については、javax.sql.rowset.JdbcRowSet
インタフェースの Javadoc (http://java.sun.com/javase/6/docs/api/javax/sql/rowset/JdbcRowSet.html
) を参照してください。
acceptChanges()
を呼び出して RowSet の変更をデータベースに伝播する場合、WebLogic Server では RowSet の元のデータ (最後の同期以降のデータ) が、データベースのデータとオプティミスティックな同時実行性ポリシーに基づいて比較されます。データの変更が検出されると、javax.sql.rowset.spi.SyncProviderException
が送出されます。デフォルトでは、アプリケーションでは何も行う必要はありませんが、RowSet の変更はデータベースと同期されません。こうした例外を処理してシステムに適した方法でデータ変更を行うように、アプリケーションを設計できます。
注意 : javax.sql.rowset.CachedRowSets については、RowSet のすべての行にあるすべての元の値が、対応するデータベースの行と比較されます。weblogic.jdbc.rowset.WLCachedRowSet 、または他の WebLogic 拡張の RowSet では、オプティミスティックな同時実行性の設定に基づいてデータが比較されます。詳細については、「オプティミスティックな同時実行性ポリシー」を参照してください。 |
SyncProviderException
を処理する主な手順は次のとおりです。
javax.sql.rowset.spi.SyncProviderException
を捕捉します。
この例外から SyncResolver オブジェクトを取得します。「SyncResolver オブジェクトの取得」を参照してください。
nextConflict()
または任意の他の移動用メソッドを使用して、衝突まで移動します。「SyncResolver オブジェクト内の移動」を参照してください。
適切な値を判断して、setResolvedValue()
(RowSet に値を設定するメソッド) で設定します。「RowSet データの同期の衝突に対する解決後の値の設定」を参照してください。
衝突している値ごとに、手順 3 と 4 を繰り返します。
RowSet に対して (SyncResolver ではなく) rowset.acceptChanges()
を呼び出し、新しい解決後の値を使用してデータベースとの間で変更を同期します。「変更の同期」を参照してください。
SyncResolver および SyncProviderException
の詳細については、RowSet の仕様または SyncResolver
インタフェースの Javadoc を参照してください。
注意 : SyncProviderException の解決を開始する前に、他のどのプロセスもデータを更新することのないようにします。 |
表 6-2 に、RowSet からデータベースにデータの変更を同期する際に起こる、衝突状況の種類について示します。
表 6-2 RowSet の変更をデータベースに同期する際の衝突の種類
RowSet データの変更の種類 | データベース データの変更の種類 | メモ |
---|---|---|
更新 |
更新 |
RowSet とデータベースの同じ行の値が変更された。SyncResolver のステータスは SyncResolver.UPDATE_ROW_CONFLICT。 場合によりアプリケーションで、この衝突を解決するロジックを提供するか、ユーザに新しいデータを提示することが必要になる。 |
更新 |
削除 |
RowSet の行の値は更新されたが、データベースではその行が削除された。SyncResolver のステータスは SyncResolver.UPDATE_ROW_CONFLICT。 場合によりアプリケーションで、行を削除されたままにしておく (データベースの状態のようにする) か、行を元に戻して RowSet からの変更を永続化するかを決定するロジックの提供が必要になる。
データベースで行が削除されている場合、衝突している値がないことに注意する。 |
削除 |
更新 |
RowSet の行は削除されたが、データベースではその行が更新された。SyncResolver のステータスは SyncResolver.DELETE_ROW_CONFLICT。 場合によりアプリケーションで、行を削除する (RowSet の状態のようにする) か、行を保持してデータベースでの現在の変更を永続化するかを決定するロジックの提供が必要になる。 この場合、行のすべての値が衝突した値になる。行を保持してデータベースの現在の値とするには、 |
削除 |
削除 |
RowSet で行が削除され、データベースでは別のプロセスでその行が削除された。Syncresolver のステータスは SyncResolver.DELETE_ROW_CONFLICT。 SyncProviderException を解決するには、RowSet のその行に対する削除処理を取り消す必要がある。 行のどのカラムにも、衝突している値がない ( |
挿入 |
挿入 |
RowSet に行が挿入され、データベースにも別の行が挿入された場合、主キーの衝突が起こることがある。その場合 SQL 例外が送出される。SyncProviderException は送出されないので、この種類の衝突を SyncResolver を使って直接的に処理することはできない。 |
コード リスト 6-5 に、RowSet とデータベースの間で衝突している値を SyncResolver を使って解決する方法についての要約されたサンプルを示します。このサンプルでは、衝突のある SyncResolver の各行について、認識されている名前を持つカラムの値をチェックします。このサンプルの詳細については、サンプルの後の節で説明します。
コード リスト 6-5 SyncResolver の要約済みサンプル コード
try { rs.acceptChanges(); } catch (SyncProviderException spex) { SyncResolver syncresolver = spex.getSyncResolver(); while (syncresolver.nextConflict()) { int status = syncresolver.getStatus(); int rownum = syncresolver.getRow(); rs.absolute(rownum); // 各カラムの null をチェックする // 衝突を書き出す // 解決後の値をこのサンプル用のデータベースの値に設定する // データベースで削除された行についての例外を処理する try { Object idConflictValue = syncresolver.getConflictValue("ID"); if (idConflictValue != null) { System.out.println("ID value in db: " + idConflictValue); System.out.println("ID value in rowset: " + rs.getInt("ID")); syncresolver.setResolvedValue("ID", idConflictValue); System.out.println("Set resolved value to " + idConflictValue); } else { System.out.println("ID: NULL - no conflict"); } } catch (RowNotFoundException e) { System.out.println("An exception was thrown when requesting a "); System.out.println("value for ID. This row was "); System.out.println("deleted in the database."); } . . . } try { rs.acceptChanges(); } catch (Exception ignore2) { } }
SyncProviderException
を処理するために、例外を捕捉してそこから SyncResolver
オブジェクトを取得できます。次に例を示します。
try { rowset.acceptChanges(); } catch (SyncProviderException spex) { SyncResolver syncresolver = spex.getSyncResolver(); . . . }
SyncResolver は、SyncResolver
インタフェースを実装する RowSet です。SyncResolver オブジェクトには、元の RowSet のすべての行に対応する行が含まれます。衝突していない値については、SyncResolver での値は null になります。衝突している値については、データベースの現在の値が値として格納されます。
SyncResolver オブジェクトを使用すると、すべての衝突に移動して、衝突している各値に適切な値を設定できます。SyncResolver インタフェースには nextConflict()
メソッドと previousConflict()
メソッドが含まれており、これらを使用して null
以外の衝突している値を持つ SyncResolver 内で、直接次の行に移動できます。SyncResolver オブジェクトは RowSet なので、SyncResolver の任意の行へのカーソル移動には、すべての RowSet 移動用メソッドも使用できます。ただし、nextConflict()
メソッドおよび previousConflict()
メソッドを使用すると、衝突している値のない行を簡単にスキップできます。
衝突している行にカーソルを移動した後には、各カラムの値を getConflictValue()
メソッドでチェックして、RowSet 内の値と衝突しているデータベース内の値を検索してから、それらの値を比較して衝突の処理方法を決定する必要があります。値が衝突していない行の場合、戻り値として null
が返されます。行がデータベースでは削除されていた場合、戻り値はなく、例外が送出されます。
注意 : WebLogic の RowSet 実装では、データベースの行の任意の値と、RowSet の作成時または最後の同期時に RowSet に読み込まれた値が異なる場合、値の衝突が発生します。 |
RowSet とデータベースの値を比較するコードのサンプルを以下に示します。
syncresolver.nextConflict() for (int i = 1; i <= colCount; i++) { if (syncresolver.getConflictValue(i) != null) { rsValue = rs.getObject(i); resolverValue = syncresolver.getConflictValue(i); . . . // RowSet および SyncResolver の値を比較して // どちらを解決後の値 (永続化する値) とするかを決定する } }
データベースに永続化するための適切な値を設定するには、setResolvedValue()
を呼び出します。次に例を示します。
syncresolver.setResolvedValue(i, resolvedValue);
setResolvedValue()
メソッドでは以下の変更が行われます。
データベースに永続化するための値を設定する。つまり、RowSet の現在の値を設定します。変更が同期されると、この新しい値がデータベースに永続化されます。
RowSet のデータの元の値をデータベースの現在の値に変更する。元の値とは、最後の同期以降に設定されていた値です。setResolvedValue()
を呼び出すと、元の値がデータベースの現在の値になります。
同期の呼び出しにおける WHERE 句を、データベースの適切な行が更新されるように変更する。
SyncResolver で衝突している値を解決した後には、変更をデータベースとの間で同期する必要があります。これを行うには、rowset.acceptChanges()
をもう一度呼び出します。acceptChanges()
呼び出しでは SyncResolver オブジェクトがクローズされ、同期の完了後にデータベースのロックが解放されます。
WLCachedRowSet は CachedRowSet、FilteredRowSet、WebRowSet、および SortedRowSet の拡張です。JoinRowSet には以下のような特性があります。
WebLogic Server の RowSet 実装では、すべての RowSet が元は WLCachedRowset として作成される。WLCachedRowSet は、それを拡張するあらゆる標準 RowSet 型として互換的に使用できます。
WLCachedRowSet には、RowSet をより簡単に扱えるようにする便利なメソッド群があり、またオプティミスティックな同時実行性オプションとデータ同期化オプションを設定するメソッド群も含まれている。
詳細については、weblogic.jdbc.rowset.WLCachedRowSet インタフェースの Javadoc を参照してください。
RowSet は 1 つのスレッドでのみ使用できます。複数のスレッドで共有することはできません。SharedRowSet は CachedRowSet を拡張したもので、元の CachedRowSet のデータに基づいて、他のスレッドで使用する CachedRowSet を追加作成できます。SharedRowSet には以下のような特性があります。
各 SharedRowSet は、元の RowSet の表層的なコピー (データのコピーではなく、元の RowSet のデータへの参照) で、独自のコンテキスト (カーソル、フィルタ、ソータ、保留中の変更、および SyncResolver) を備えている。
任意の SharedRowSet で行われたデータの変更がデータベースとの間で同期されると、基底の CachedRowSet も更新される。
SharedRowSet を使用すると、アプリケーションで必要とするデータベースへの往復回数が減るため、パフォーマンスが向上する可能性がある。
SharedRowSet を作成するには、WLCachedRowSet インタフェースの createShared()
メソッドを使用して、その結果を WLCachedRowSet 型にキャストします。次に例を示します。
WLCachedRowSet sharedrowset = (WLCachedRowSet)rowset.createShared();
SortedRowSet は CachedRowSet を拡張して、CachedRowSet の行をアプリケーションで提供される Comparator オブジェクトに基づいてソートできるようにしたものです。SortedRowSet には以下のような特性があります。
ソート処理の設定方法は、FilteredRowSet でのフィルタ処理の設定方法と同様。ただし、ソート処理は javax.sql.rowset.Predicate
オブジェクトではなく java.util.Comparator
オブジェクトに基づきます。
アプリケーションで、目的のソート動作を行う Comparator
オブジェクトを作成します。
続いてアプリケーションで setSorter(java.util.Comparator)
メソッドを使用してソート条件を設定します。
ソート処理はメモリ内で行われ、データベース管理システムには依存しない。SortedRowSet を使用することによりデータベースへの往復回数が減るため、アプリケーションのパフォーマンスが向上する可能性があります。
WebLogic Server には java.util.Comparator
を実装する SQLComparator オブジェクトが用意されている。これを使用して、ソート条件として使うカラムのリストを渡すことで、SortedRowSet の行をソートできます。次に例を示します。
rs.setSorter(new weblogic.jdbc.rowset.SQLComparator("columnA,columnB,columnC"));
詳細については、Javadocs で以下について参照してください。
ここでは、SQLPredicate
クラスについて説明します。
Weblogic Server には javax.sql.rowset.Predicate
インタフェースの実装である weblogic.jdbc.rowset.SQLPredicate
クラスが用意されています。SQLPredicate
クラスを使用すると、SQL のような WHERE 句の構文を使用して FilteredRowSet のフィルタを定義できます。
SQLPredicate クラスの文法は JMS セレクタの文法から借用されたもので、SQL SELECT 文の WHERE 句の文法とよく似ています。
重要な注意事項を以下に示します。
カラムを参照する場合、カラム名を使用する必要がある。カラムのインデックス番号は使用できません。
この文法では、演算子および数学演算がサポートされる。たとえば次のようなことができます。
(colA + ColB) >=100.
WHERE 句の作成では、単純なデータ型のみを使用できる。以下のようなデータ型を使用できます。
String
Int
Boolean
Float
以下のような複合データ型はサポートされない。
Array
BLOB
CLOB
Date
// フ ィ ル タ を 設 定 // SQLPredicate クラスを使用して SQLPredicate オブジェクトを作成してから // そのオブジェクトを setFilter メソッドに渡して RowSet をフィルタ処理する SQLPredicate filter = new SQLPredicate("ID >= 103"); rs.setFilter(filter);
詳細については、weblogic.jdbc.rowset.SQLPredicate クラスの Javadoc を参照してください。
ほとんどの場合、RowSet へのデータの入力とデータベースの更新は別々のトランザクションで発生します。データベース内の基底のデータは 2 つのトランザクションの間に変更される可能性があります。WebLogic Server の RowSet 実装 (WLCachedRowSet) では、オプティミスティックな同時実行性の制御を使用して、データベースの一貫性を確保します。
オプティミスティックな同時実行性の場合、RowSet は複数のユーザが同じデータを同時に変更する可能性は低いという前提で動作します。そのため、接続されていない RowSet モデルの一端として、RowSet ではデータベース リソースがロックされません。ただし、データベースに変更を書き込む前に、データベース内の変更予定のデータが、そのデータが RowSet に読み込まれたときから変更されていないことを RowSet でチェックする必要があります。
RowSet から発行される UPDATE 文と DELETE 文には、データベース内のデータを、RowSet の入力時に読み込まれたデータに照らして検証するための WHERE 句が含まれます。RowSet ではデータベース内の基底のデータが変更されたことが検出されると OptimisticConflictException
が発行されます。アプリケーションではこの例外を捕捉して処理方法を決定できます。一般的には、アプリケーションで、変更されたデータを更新してユーザに再度提示します。
WLCachedRowSet 実装には複数のオプティミスティックな同時実行性ポリシーが用意されています。これらのポリシーによって、基底のデータベースのデータを検証するために RowSet から発行される SQL が決まります。
VERIFY_READ_COLUMNS
VERIFY_MODIFIED_COLUMNS
VERIFY_SELECTED_COLUMNS
VERIFY_NONE
VERIFY_AUTO_VERSION_COLUMNS
VERIFY_VERSION_COLUMNS
これらのポリシーの違いを例示するために、以下のような内容の例を使用します。
3 つのカラムがある簡単な従業員テーブル
CREATE TABLE employees ( e_id integer primary key, e_salary integer, e_name varchar(25) );
テーブル内の 1 つの行
e_id = 1, e_salary = 10000, and e_name = 'John Smith'
以下に示すオプティミスティックな同時実行性ポリシーのそれぞれの例では、RowSet に従業員テーブルからこの行が読み込まれ、John Smith の給与が 20000 に設定されます。続いて、オプティミスティックな同時実行性ポリシーが RowSet から発行される SQL コードにどのように影響するかが示されます。
デフォルトでは、RowSet のオプティミスティックな同時実行性制御ポリシーは VERIFY_READ_COLUMNS です。RowSet から UPDATE または DELETE が発行されるときに、データベースから読み込まれたすべてのカラムが WHERE 句に含まれます。これによって、RowSet に最初に読み込まれたすべてのカラムの値について変更されてないかどうかが検証されます。
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1 AND e_salary=10000 AND e_name = 'John Smith';
VERIFY_MODIFIED_COLUMNS ポリシーでは、主キー カラムと更新されるカラムのみが WHERE 句に含まれます。アプリケーションで更新されるカラムの一貫性だけを考慮する場合に便利です。データを読み込んだ後に更新の対象外のカラムが変更されていても、この更新をコミットできます。
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1 AND e_salary=10000
e_id
カラムは主キー カラムなので含まれています。e_salary
カラムは更新されるカラムなので、同様に含まれています。e_name
カラムは読み込まれただけなので、検証されません。
VERIFY_SELECTED_COLUMNS では、主キー カラムと指定するカラムが WHERE 句に含まれます。
WLRowSetMetaData metaData = (WLRowSetMetaData) rowSet.getMetaData(); metaData.setOptimisticPolicy(WLRowSetMetaData.VERIFY_SELECTED_COLUMNS); // e_salary カラムのみを検証する metaData.setVerifySelectedColumn("e_salary", true); metaData.acceptChanges();
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1 AND e_salary=10000
e_id
カラムは主キー カラムなので含まれています。e_salary
カラムは選択されたカラムなので、同様に含まれています。
VERIFY_NONE ポリシーでは、主キー カラムのみが WHERE 句に含まれます。データベース データに対して追加で検証は行われません。
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1
VERIFY_AUTO_VERSION_COLUMNS では、主キー カラムと、別個のバージョン カラムが WHERE 句に含まれます。RowSet では更新の一環としてバージョン カラムの自動的なインクリメントも行われます。このバージョン カラムのデータ型は integer でなければなりません。データベース スキーマを更新して、別個のバージョン カラム (e_version
) を含める必要があります。例では、このカラムの現在の値が 1 であると仮定します。
metaData.setOptimisticPolicy(WLRowSetMetaData. VERIFY_AUTO_VERSION_COLUMNS); metaData.setAutoVersionColumn("e_version", true); metaData.acceptChanges();
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000, e_version = 2 WHERE e_id = 1 AND e_version = 1
e_version
カラムは SET 句で自動的にインクリメントされます。WHERE 句は主キー カラムとバージョン カラムを検証しています。
VERIFY_VERSION_COLUMNS の場合、RowSet では主キー カラムと、別個のバージョン カラムがチェックされます。RowSet では更新の一環としてバージョン カラムはインクリメントされません。データベース スキーマを更新して、別個のバージョン カラム (e_version
) を含める必要があります。例では、このカラムの現在の値が 1 であると仮定します。
metaData.setOptimisticPolicy(WLRowSetMetaData.VERIFY_VERSION_COLUMNS); metaData.setVersionColumn("e_version", true); metaData.acceptChanges();
この例では、RowSet から次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1 AND e_version = 1
WHERE 句では主キー カラムとバージョン カラムが検証されます。RowSet でバージョン カラムがインクリメントされないため、この処理はデータベースで行う必要があります。一部のデータベースでは、行の更新時に自動的にインクリメントするバージョン カラムを提供しています。データベース トリガを使用してこのタイプの更新を処理することもできます。
オプティミスティックなポリシーでは、変更する行に対する UPDATE 文と DELETE 文のみが検証されます。読み込み専用の行はデータベースに照らして検証されません。
ほとんどのデータベースでは WHERE 句での BLOB または CLOB カラムの使用が許可されていないため、RowSet では BLOB または CLOB カラムの検証はできません。
RowSet に複数のテーブルが含まれる場合、RowSet では更新されたテーブルのみが検証されます。
デフォルトの VERIFY_READ_COLUMNS では、パフォーマンスを多少犠牲にして、強力なレベルの一貫性が提供されます。最初に読み込まれたすべてのカラムをデータベースに送信したり、データベースと比較したりする必要があるため、このポリシーでは多少のオーバーヘッドが追加で発生します。VERIFY_READ_COLUMNS は強力なレベルの一貫性が必要な場合に適しています。バージョン カラムを含めるようにデータベース テーブルを変更することはできません。
VERIFY_SELECTED_COLUMNS は、開発者が検証を完全に制御する必要があり、アプリケーションに固有の知識を使用して SQL を微調整する場合に便利です。
VERIFY_AUTO_VERSION_COLUMNS では、VERIFY_READ_COLUMNS と同じレベルの一貫性が提供されますが、integer 型の 1 つのカラムを比較するだけで済みます。このポリシーではバージョン カラムのインクリメントも行われるため、データベースの設定が若干必要です。
VERIFY_VERSION_COLUMNS は、最高レベルのパフォーマンスと一貫性が必要なプロダクション システムにお勧めします。VERIFY_AUTO_VERSION_COLUMNS と同様に、高いレベルの一貫性が提供されますが、データベースで 1 つのカラムの比較が行われるだけです。VERIFY_VERSION_COLUMNS では、データベースでバージョン カラムのインクリメントを行う必要があります。一部のデータベースでは、更新時に自動的にインクリメントするカラム タイプを提供していますが、この動作はデータベース トリガを使用して実装することもできます。
VERIFY_MODIFIED_COLUMNS と VERIFY_NONE では一貫性が確保される程度は低下しますが、オプティミスティックな衝突が起きる可能性も少なくなります。高レベルなデータの一貫性の必要性よりも、パフォーマンス、衝突の回避の方が重要な場合は、これらのポリシーを検討します。
RowSet を使用する場合は以下のパフォーマンス オプションを検討します。
RowSet 実装では JDBC のバッチ処理もサポートされています。各 SQL 文を JDBC ドライバに個々に送信する代わりに、バッチ処理では文の集合を 1 つの一括処理として JDBC ドライバに送信します。バッチ処理はデフォルトで無効になっていますが、一般に 1 つのトランザクションで多数の更新が発生する場合にパフォーマンスが向上します。アプリケーションとデータベースに対してこのオプションを有効および無効に設定して、ベンチマークを実行してみるとよいでしょう。
WLCachedRowSet インタフェースには、INSERT、DELETE、および UPDATE 文のバッチ処理を制御するための setBatchInserts(boolean)
、setBatchDeletes(boolean)
、および setBatchUpdates(boolean)
メソッドがあります。
注意 : setBatchInserts 、setBatchDeletes 、setBatchUpdates メソッドは、acceptChanges メソッドを呼び出す前に呼び出す必要があります。 |
WLCachedRowSet はオプティミスティックな同時実行性制御に依存しているため、更新または削除コマンドが成功したか、オプティミスティックな衝突が発生したかを判別する必要があります。WLCachedRowSet 実装では、文によって更新された行の数についての JDBC ドライバの報告に基づいて、衝突が発生したかどうかを判別します。更新された行が 0 の場合、WLCachedRowSet では衝突が発生したと認識されます。
Oracle JDBC ドライバではバッチ更新が実行されると java.sql.Statement.SUCCESS_NO_INFO
が返されるため、RowSet 実装では衝突が発生したかどうかを判別するために戻り値を使用できません。
バッチ処理が Oracle データベースで使用されることが RowSet で検出された場合、バッチ処理の動作は自動的に変更されます。
バッチ挿入は検証されないため、通常どおり実行されます。
バッチ更新は正常に実行されますが、RowSet では特別な SELECT クエリが発行され、バッチ更新でオプティミスティックな衝突が発生したかどうかがチェックされます。
SELECT 検証クエリの後でバッチ削除を実行するよりも効率的なため、バッチ削除ではグループ削除が使用されます。
複数の行を削除する場合、RowSet では通常、削除する行ごとに DELETE 文が発行されます。グループ削除を有効にした場合、RowSet では削除する行を含む WHERE 句が指定された DELETE 文が 1 つだけ発行されます。
たとえば、テーブルから 3 人の従業員を削除する場合、通常であれば RowSet から次のように発行されます。
DELETE FROM employees WHERE e_id = 3 AND e_version = 1; DELETE FROM employees WHERE e_id = 4 AND e_version = 3; DELETE FROM employees WHERE e_id = 5 AND e_version = 10;
グループ削除を有効にした場合、RowSet から次のように発行されます。
DELETE FROM employees WHERE e_id = 3 AND e_version = 1 OR e_id = 4 AND e_version = 3 OR e_id = 5 AND e_version = 10;
WLRowSetMetaData.setGroupDeleteSize
を使用すると、1 つの DELETE 文に含まれる行の数を決定できます。デフォルト値は 50 です。