8 WebLogic ServerにおけるRowSetの使い方
非推奨のweblogic.jdbc.rowsets
weblogic.jdbc.rowset
インタフェースおよびクラスはWebLogic Server 12.1.2では非推奨となりました。
J2SE JRE/SDKのリファレンス実装com.sun.rowset
を使用してください。weblogic.jdbc.rowset
を参照してください。
RowSetについて
RowSetはJava ResultSetの拡張です。ResultSetと同様、RowSetも表形式のデータを保持するJavaオブジェクトです。ただし、RowSetではResultSet機能に大幅な柔軟性が加えられていて、ResultSetの持つ制限の一部が緩和されたり削減されたりしています。
WebLogic Serverには、JSR-114の仕様に準拠したJava RowSetが実装されています。仕様の詳細は、http://www.oracle.com/technetwork/java/javase/jdbc/index.html
を参照してください。WebLogicのRowSet実装には、RowSet仕様の拡張も含まれています。こうした拡張を使用すると、アプリケーションでRowSetをより有効に活用できます。
RowSetの種類
WebLogic ServerのRowSet実装には、標準のRowSetの種類およびWebLogic RowSet拡張が含まれています。
標準のRowSet:
WebLogicのRowSet拡張:
RowSetを使用したプログラミング
WebLogicのRowSet実装にはライフ・サイクルのフレームワークがあり、これによってRowSetオブジェクトが異常な状態にならないようにしています。
WebLogic ServerのRowSet実装は、ユーザーが以下の方法でRowSetを扱うことを想定して設計されています。
- RowSetを作成して構成します(問合せ、データベース接続、その他のプロパティを定義します)。
- RowSetにデータを入力します(問合せパラメータを指定して問合せを実行します)。
- 必要に応じてRowSetメタデータを指定します。
- 必要に応じてRowSetにフィルタまたはソーターを設定します。
- RowSet内のデータを操作します(挿入、更新、削除を行います)。
- RowSetからデータベースへのデータ変更の同期を行います。
変更を同期した後には、アプリケーションの設計に応じてステップ2またはステップ3から繰返し処理を行えます。「トランザクション完了後のWebLogic RowSetの再利用」を参照してください。
WebLogic Serverでは、前述したプロセスどおりにRowSetが移動するように、内部的に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のライフ・サイクル・ステージの実例については、例8-1のコメントを参照してください。
CachedRowSet
WebLogic Serverにおける標準のCachedRowSetの使い方について学習します。
また、標準のCachedRowSetオブジェクトのWebLogic拡張の使用方法については、「WLCachedRowSet」を参照してください。
特性
CachedRowSetは接続されていないResultSet
オブジェクトです。CachedRowSetのデータはメモリーに格納されます。WebLogic Server実装のCachedRowSetには以下の特性があります。
-
データの挿入、更新、削除に使用できます。
-
シリアライズ可能なため、モバイル機器などの様々なアプリケーション・コンポーネントに渡すことができます。
-
トランザクション処理に対応しているためRowSetを再利用できます。「トランザクション完了後のWebLogic RowSetの再利用」を参照してください。
-
RowSetでのデータ変更をデータベースとの間で同期するためにオプティミスティックな同時実行性制御が使用されます。
-
SyncProvider例外から得られるSyncResolverオブジェクトを使用して、RowSetでのデータ変更とデータベース間の競合を解決します。「SyncResolverを使用したSyncProviderExceptionの処理」を参照してください。
CachedRowSetに特有のプログラミング上の考慮事項と制限
アプリケーションの設計時には、次のことを考慮するようにします。
-
RowSetの問合せ結果はすべてメモリーに格納される
-
データの競合
RowSetの問合せ結果はすべてメモリーに格納される
CachedRowSetではデータベースへの接続が保持されないので、すべての問合せの結果は必ずメモリーに格納されます。問合せの結果が非常に大きいと、メモリー不足エラーによりパフォーマンスが低下することがあります。大きなデータ・セットの場合、ResultSetを使用する方が適していることもあります。ResultSetではデータベースへの接続が保持されるため、問合せ結果の一部をメモリーに保持してから必要に応じてデータベースに戻り、さらに行を取得できるためです。
データの競合
CachedRowSetは、RowSetへの入力からRowSet内のデータ変更がデータベースとの間で同期されるまでの間に、別のプロセスで更新されることがあまりないようなデータに使用するのがもっとも適しています。この期間にデータベースが変更されるとデータの競合が発生します。データの競合を検出して処理する方法については、「SyncResolverを使用したSyncProviderExceptionの処理」を参照してください。
サンプル・コード
例8-1にCachedRowSetの基本的なワークフローを示します。この例では、主要な各操作と対応するRowSetのライフ・サイクルのステージについてコメントを使って説明します。このサンプル・コードの後で、サンプルの主要な各セクションについてさらに詳細に説明します。
例8-1 CachedRowSetのサンプル・コード
import javax.sql.rowset.CachedRowSet; import javax.sql.rowset.RowSetFactory; public class CachedRowSetDemo { public static void main (String[] args) { //DESIGNING lifecycle stage - Create the rowset and set properties try { //Create a RowSetFactory instance and from the factory, //create a FilteredRowSet. RowSetFactory rsfact = RowSetProvider.newFactory("weblogic.jdbc.rowset.JdbcRowSetFactory",null); CachedRowSet rs = rsfact.createCachedRowSet(); //Set database access through a DataSource. rs.setDataSourceName(examples-dataSource-demoPool); //See Database Connection Options for more options. //Set query command rs.setCommand("SELECT ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, PHONE, EMAIL FROM PHYSICIAN WHERE ID>?"); //CONFIGURE QUERY lifecycle operation rs.setInt(1, 0); //POPULATING lifecycle stage - Execute the command to populate the rowset rs.execute(); } //CONFIGURING METADATA - Populate first, then set MetaData, //including KeyColumns rs.setKeyColumns(new int[] { 1 }); while (rs.next ()) //NAVIGATING lifecycle stage { 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)); } } //Working with data //Delete rows in the rowset try { //MANIPULATING lifecycle stage - navigate to a row //(manually moving the cursor) rs.last(); rs.deleteRow(); //Note that the database is not updated yet. } //Update a row in the rowset try { //MANIPULATING lifecycle stage - navigate to a row //(manually moving the cursor) rs.first(); //UPDATING lifecycle stage - call an update() method rs.updateString(4, "Francis"); //MANIPULATING lifecycle stage - finish update rs.updateRow(); //Note that the database is not updated yet. } //INSERTING lifecycle stage - Insert rows in the 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(); //"Finish Update" action; //MANIPULATING lifecycle stage - navigate to a row rs.moveToCurrentRow(); //Note that the database is not updated yet. } //Send all changes (delete, update, and insert) to the database. //DESIGNING or POPULATING lifecycle stage - after synchronizing changes //with the database, lifecycle stage depends on other environment settings. //See Reusing a WebLogic RowSet After Completing a Transaction. try { rs.acceptChanges(); rs.close(); } }
CachedRowSetのクラスとインタフェースのインポート
標準のRowSetを使用するには、次のクラスをインポートする必要があります。
javax.sql.rowset.CachedRowSet; javax.sql.rowset.RowSetFactory;
CachedRowSetのプロパティの設定
RowSetには、同時実行性の種類、データ・ソースの名前、トランザクション分離レベルなど数多くのプロパティがあります。これらのプロパティを設定してRowSetの動作を決定できます。RowSetの特定の用途に応じて必要なプロパティのみを設定する必要があります。利用可能なプロパティの詳細は、http://docs.oracle.com/javase/1.5.0/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()
メソッドのパラメータとして渡します。必要に応じて接続を閉じる必要もあります。//Lookup DataSource and get a connection ctx = new InitialContext(ht); javax.sql.DataSource ds = (javax.sql.DataSource) ctx.lookup ("myDS"); conn = ds.getConnection(); //Pass the connection to the rowset rs.execute(conn);
JDBCデータ・ソースの詳細については、「DataSourceオブジェクトからのデータベース接続の取得」を参照してください
-
JDBCドライバをロードして直接的に接続する - JDBCドライバをロードして適切なプロパティを設定すると、RowSetでは
execute()
およびacceptChanges()
を呼び出したときにデータベース接続が作成されます。RowSetでは接続を使用し終えるとすぐにその接続が閉じられます。RowSetではexecute()
メソッド呼出しとacceptChanges()
メソッド呼出しの間、接続は保持されません。Class.forName("org.apache.derby.jdbc.ClientDriver"); rs.setUrl("jdbc:derby://localhost:1527/demo"); rs.setUsername("examples"); rs.setPassword("examples"); rs.execute();
CachedRowSetへの入力
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
の結果セットを使用している場合、以下の条件で行セットに入力しようとするとSQLExceptionがスローされます。-
結果セットのカーソルが行1を超える位置にある状態で
CachedRowset.populate(ResultSet rs)
を呼び出した場合。 -
newPosition
の値が結果セットの現在のカーソル位置より小さい状態でCachedRowset.populate(ResultSet rs, int newPosition)
を呼び出した場合。
-
CachedRowSetへのメタデータの設定
RowSetのデータ変更をデータベースとの間で同期するために、RowSetにメタデータを設定することが必要な場合もあります。「RowSetに対するデータベース更新用メタデータの設定」を参照してください。
CachedRowSetのデータの操作
キャッシュされたRowSetに一連のデータ行を入力した後には、結果セットのデータを扱うのとほぼ同じ方法でそのキャッシュ済みのデータを取り扱えます。ただし、データベースに対して変更を行う前には、acceptChanges()
を明示的に呼び出す必要があります。
ノート:
RowSetの列名や表名に区切り識別子は使用できません。SQL文の中では、区切り識別子は二重引用符で囲む必要があります。区切り識別子には、SQL予約語(USER
やDATE
など)になっているものや、識別子ではない名前になっているものがあります。有効な識別子は必ず文字で始まり、文字、数字、およびアンダースコアのみを使用できます。
RowSetの行からデータを取得する
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")); }
RowSetの行を更新する
通常、データの更新は以下の手順で行います。
- 行または挿入行に移動します。
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文で列の索引番号を使用する必要があります。
RowSetに行を挿入する
行を挿入するには、新規の挿入行にカーソルを移動してその行の列の値を更新してから、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の変更をデータベースとの間で同期する
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();
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
インタフェースの説明を参照してください。
メタデータを扱うためのWebLogic RowSet拡張
RowSetに対する適切なメタデータの取得または設定に使用できるWebLogic RowSet拡張について学習します。
executeAndGuessTableNameとexecuteAndGuessTableNameAndPrimaryKeys
SQL問合せでRowSetに入力する場合、通常はexecute()
メソッドを使用して問合せを実行し、データを読み込みます。WLCachedRowSet
実装には、関連付けられた表のメタデータも識別するためにexecute
メソッドを拡張したexecuteAndGuessTableName
メソッドおよびexecuteAndGuessTableNameAndPrimaryKeys
メソッドが用意されています。
executeAndGuessTableName
メソッドでは、関連付けられたSQLを解析して、すべての列の表名を、SQLキーワードFROM
に続く最初の語として設定します。
executeAndGuessTableNameAndPrimaryKeys
メソッドは、SQLコマンドを解析して表名を読み取ります。続いて、java.sql.DatabaseMetaData
を使用して表の主キーを判定します。
ノート:
これらのメソッドはDBMSまたはJDBCドライバのサポートに依存しています。すべてのDBMS、すべてのJDBCドライバで機能するとは限りません。
MetaDataインタフェースを使用した表および主キー情報の設定
WLRowSetMetaData
インタフェースを使用して表および主キー情報を手動で設定することもできます。
WLRowSetMetaData metaData = (WLRowSetMetaData) rowSet.getMetaData(); // Sets one table name for all columns metaData.setTableName("employees");
または
metaData.setTableName("e_id", "employees"); metaData.setTableName("e_name", "employees");
WLRowSetMetaData
インタフェースを使用して主キー列を指定することもできます。
metaData.setPrimaryKeyColumn("e_id", true);
Oracle WebLogic Server Java APIリファレンスのweblogic.jdbc.rowset.WLRowSetMetaData
を参照してください。
書込み表の設定
WLRowSetMetaData
インタフェースには、更新または削除する表のみを指定するためのsetWriteTableName
メソッドがあります。このメソッドは通常、複数の表の結合が入力されているRowSetにおいて、1つの表のみを更新する場合に使用します。書込み表に属さない列は読取り専用としてマークされます。
たとえば、RowSetに「注文」と「顧客」の結合が含まれるとします。書込み表を「注文」に設定します。deleteRowを呼び出すと、「注文」の行は削除されますが、「顧客」の行は削除されません。
ノート:
JSR-114は、WebLogicのCachedRowSetMetaData.setWriteTableName
メソッドと同じ機能を備えたCachedRowSet.setTableName
(http://docs.oracle.com/javase/8/docs/api/javax/sql/rowset/CachedRowSet.html#setTableName(java.lang.String)
を参照)を提供します。どちらのメソッドを呼び出しても、書込み表に属さない列が読取り専用のものとしてマークされます。WebLogicには、列がどの表に属するかのマッピングに使用されるCachedRowSetMetaData.setTableName
メソッドも用意されています。setTableName
を使用して書込み表を設定する際には、アプリケーションに適したAPIを使用してメソッドを実装するように注意してください。
RowSetとトランザクション
ほとんどのデータベースやJDBCアプリケーションではトランザクションを使用します。RowSetはJTAトランザクションを含むトランザクションをサポートしています。
一般的な使用例は次のとおりです。トランザクション1でRowSetを取得します。トランザクション1がコミットされます。基底のデータに対して、データベースやアプリケーション・サーバーによるロックはありません。
RowSetはメモリー内にデータを保持します。データを変更したり、ネットワーク経由でクライアントに提供したりできます。アプリケーションはデータベースに対する変更をコミットする場合、トランザクション2を開始して、RowSetのacceptChanges
メソッドを呼び出します。その後トランザクション2がコミットされます。
JTAグローバル・トランザクションとの統合
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の呼出し
この状況では、接続オブジェクトはRowSetによって作成されたものではなく、connection.commit
を呼び出すことによってローカル・トランザクションを開始します。トランザクションが失敗するか、または接続がconnection.rollback
を呼び出すと、データはデータベースからロールバックされますが、RowSetにおいてはロールバックされません。作業を進める前に、次のいずれかを行う必要があります。
-
rowset.refresh
を呼び出して、データベースからのデータでRowSetを更新します。 -
現在のデータで新しいRowSetを作成します。
トランザクション完了後のWebLogic 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を変更できるようになります。
FilteredRowSet
WebLogic Serverにおける標準のFilteredRowSetの使い方について学習します。
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フィルタ」を参照してください。
特有のプログラミング上の考慮事項
RowSetフィルタは累積的ではない
FilteredRowSetのWebLogic実装では現在、FilteredRowSetにフィルタを2回設定すると古いフィルタが新しいフィルタで置き換えられます。JSR-114にはこの点についてはっきりとした規定がありません。参照実装は同じように動作しません。行セット内のフィルタリングされた行をさらにフィルタリングします。この場合2回目のフィルタを変更して必要なすべての条件をフィルタするようにすると、同じ効果を得られます。
フィルタの設定、変更前に保留中の変更を解決する
RowSetフィルタを設定または変更する前にRowSetに保留中の変更がある場合、その変更を受け入れる(acceptChanges()
を呼び出す)か、RowSetのデータを変更前の状態に戻す(restoreOriginal()
を呼び出す)必要があります。WebLogic Serverでは行われる可能性のある変更を示せるようにRowSet内の移動が認められており、ユーザーはRowSetフィルタを変更する前に上記のどちらかのメソッドを呼び出す必要があります。acceptChanges()
ではデータベースへのアクセスが行われ、restoreOriginal()
では行われません。
FilteredRowSetのサンプル・コード
以下のサンプルに、キャッシュされたRowSetを作成してからWebLogic Server SQLPredicateを使用してフィルタを適用および変更する方法について示します。
例8-2 FilteredRowSetのサンプル・コード
import javax.sql.rowset.FilteredRowSet; import javax.sql.rowset.RowSetFactory; import weblogic.jdbc.rowset.SQLPredicate; public class FilteredRowSetDemo { public static void main (String[] args) { //DESIGNING lifecycle stage - Create the rowset and set properties try { //Create a RowSetFactory instance and from the factory, //create a FilteredRowSet. RowSetFactory rsfact = RowSetProvider.newFactory("weblogic.jdbc.rowset.JdbcRowSetFactory",null); FilteredRowSet rs = rsfact.createFilteredRowSet(); //Set database access through a DataSource. //See Database Connection Options for more options. rs.setDataSourceName(examples-dataSource-demoPool); rs.setCommand("SELECT ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, PHONE, EMAIL FROM PHYSICIAN WHERE ID>?"); //CONFIGURE QUERY lifecycle operation - set values for query parameters. rs.setInt(1, 0); //POPULATING lifecycle stage - Execute the command to populate the rowset rs.execute(); } //CONFIGURING METADATA - Populate first, then set MetaData, including KeyColumns rs.setKeyColumns(new int[] { 1 }); while (rs.next ()) //NAVIGATE operations put the rowset in the MANIPULATING lifecycle stage { 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)); } } //Need to accept changes or call restoreOriginal to put the rowset //into the DESIGNING or POPULATING stage. //After navigating, the rowset is in MANIPULATING stage, //and you cannot change properties in that lifecycle stage. rs.restoreOriginal(); //S E T F I L T E R //use SQLPredicate class to create a SQLPredicate object, //then pass the object in the setFilter method to filter the 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 (" "); } //Need to accept changes or call restoreOriginal to put the rowset //into the DESIGNING or POPULATING lifecycle stage. //After navigating, the rowset is in MANIPULATING stage, //and you cannot change properties in that lifecycle stage. rs.restoreOriginal(); //C H A N G I N G F I L T E R 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 (" "); } //Need to accept changes or call restoreOriginal to put the rowset //into the DESIGNING or POPULATING lifecycle stage. //After navigating, the rowset is in MANIPULATING stage, //and you cannot change properties in that lifecycle stage. rs.restoreOriginal(); //R E M O V I N G F I L T E R 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のクラスとインタフェースのインポート
標準のFilteredRowSetを使用するには、次のクラスをインポートする必要があります。
javax.sql.rowset.FilteredRowSet; javax.sql.rowset.RowSetFactory;
上記のサンプル・コードでは、フィルタの作成にweblogic.jdbc.rowset.SQLPredicate
クラスも使用されています。アプリケーションでは、weblogic.jdbc.rowset.SQLPredicate
クラスを使用することも、独自のフィルタ・クラスを作成することも可能です。「FilteredRowSetに対するフィルタの設定」を参照してください。
FilteredRowSetの作成
RowSetはファクトリ・インタフェースから作成されます。WebLogic ServerでFilteredRowSetを作成するには、主に以下のステップに従います。
FilteredRowSetのプロパティの設定
FilteredRowSetのプロパティのオプションはCachedRowSetのオプションと同じです。「CachedRowSetのプロパティの設定」を参照してください。
FilteredRowSetのデータベース接続のオプション
FilteredRowSetのデータベース接続のオプションはCachedRowSetのオプションと同じです。「データベース接続のオプション」を参照してください。
FilteredRowSetのメタデータの設定
RowSetのデータ変更をデータベースとの間で同期するために、RowSetにメタデータを設定することが必要な場合もあります。「RowSetに対するデータベース更新用メタデータの設定」を参照してください。
FilteredRowSetに対するフィルタの設定
FilteredRowSetの行をフィルタするには、setFilter
メソッドを呼び出して、Predicate (フィルタ)オブジェクトをこのメソッドのパラメータとして渡します。Predicateオブジェクトはjavax.sql.rowset.Predicate
インタフェースを実装するクラスのインスタンスです。FilteredRowSetのWebLogic実装では、独自のフィルタを定義することも、weblogic.jdbc.rowset.SQLPredicate
クラスのインスタンスを使用することもできます。
ユーザー定義のRowSetフィルタ
FilteredRowSet用のフィルタを定義する場合、主に以下のステップに従います。
- 使用するフィルタリング
動作
(表示する行を特定の列の値を持つ行に制限するなど)を行うjavax.sql.rowset.Predicateインタフェースを実装するクラスを定義します。たとえば、ID列の値の範囲に基づいて表示される行を制限できます。定義するクラスには、ID列の値をフィルタリングするためのロジックが含まれます。 - クラスのインスタンス(フィルタ)を作成して、使用するフィルタ条件を指定します。たとえば、ID列の値が100から199までの行のみを表示するといった指定が可能です。
rowset.setFilter()
を呼び出して、クラスをこのメソッドのパラメータとして渡します。
例8-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; //Constructor to create case-insensitive column - value comparison. 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."); } }
例8-4 FilteredRowSet用のフィルタを設定するコード
SearchPredicate pred = new SearchPredicate(ROWSET_LASTNAME, lastName); rs.setFilter(pred);
例8-3に、javax.sql.rowset.Predicate
インタフェースを実装するクラスのサンプルを示します。このサンプルでは、列の値の大文字と小文字を区別しないバージョンを評価するフィルタを作成することができるクラスが示されます。例8-4に、フィルタ条件を判断するクラスのインスタンスを作成してから、FilteredRowSet用のフィルタとしてフィルタ・オブジェクトを設定するコードを示します。
WebLogic SQL方式フィルタ
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のデータの操作
FilteredRowSetのデータの操作はCachedRowSetのデータの操作とほぼ同様です。ただし、行を挿入したり更新したりする場合には、必ずフィルタ条件の範囲内で変更を行って、操作した行が表示される行の集合に残るようにします。たとえば、RowSetのフィルタでID列の値が105より小さい行のみを表示できる場合、ID列の値が106の行を挿入しようとしたり、IDの値を106に更新しようとしたりすると、操作は失敗してSQLExceptionがスローされます。
データ操作の詳細については、「CachedRowSetのデータの操作」を参照してください。
WebRowSet
WebRowSetは、RowSetをXMLフォーマットで読み書きできるキャッシュされたRowSetです。
WebRowSetには以下のような特性があります。
-
readXml(java.io.InputStream iStream)
メソッドを使用してXMLソースからRowSetに入力します。 -
writeXml(java.io.OutputStream oStream)
メソッドを使用してデータおよびメタデータをXMLで書き込み、他のアプリケーション・コンポーネントで使用できるように、またはリモート・クライアントに送信できるようにします。 -
RowSetへの入力に使用されるXMLコード、またはRowSetから記述されるXMLコードは、
http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/jdbc/webrowset.xsd
で利用可能な標準のWebRowSet XMLスキーマ定義に準拠します。
http://www.oracle.com/technetwork/java/javase/jdbc/index.html
およびjavax.sql.rowset.WebRowSet
インタフェースのJavadoc (http://docs.oracle.com/javase/8/docs/api/javax/sql/rowset/WebRowSet.htm
)を参照してください。
ノート:
WebLogic ServerではRowSetについて2つのスキーマがサポートされています。1つは標準WebRowSet用、もう1つはWLCachedRowSet用です。これは、JSR-114の最終仕様より前に実装されたものです。
特有のプログラミング上の考慮事項
-
WebLogicのWebRowSet実装では2つのXMLスキーマ(およびAPI)がサポートされます。1つは標準のWebRowSet仕様で(
http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/jdbc/webrowset.xsd
で入手可能)、もう1つはWLCachedRowSet用です。これは、JSR-114の最終仕様より前に実装されたものです。 -
WebLogic Server行セットのみを使用している場合、いずれかのスキーマを使用できます。固有スキーマにはより多くの要素タイプがあります。
-
他のRowSetの実装と対話するには、標準のスキーマを使用する必要があります。
JoinRowSet
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つのデータ型に制限されています。表8-1に、JoinRowSetの一致する列で使用できるデータ型の詳細について示します。
表8-1一致する列で使用できるデータ型
結合の左側のデータ型 | 結合の右側で使用できるデータ型 |
---|---|
Number |
Number String |
Boolean |
Boolean String |
Date |
Date String |
String |
String Number Boolean Date |
JoinRowSetの詳細については、javax.sql.rowset.Joinable
(http://docs.oracle.com/javase/8/docs/api/javax/sql/rowset/Joinable.html
)およびJoinRowSet
インタフェース(http://docs.oracle.com/javase/8/docs/api/javax/sql/rowset/JoinRowSet.html
)のJavadocを参照してください。
JDBCRowSet
javax.sql.rowset.JdbcRowSet
インタフェースのJavadoc (http://docs.oracle.com/javase/8/docs/api/javax/sql/rowset/JdbcRowSet.html
)を参照してください。
SyncResolverを使用したSyncProviderExceptionの処理
SyncResolverを使用したSyncProviderException
の処理ステップについて学習します。SyncProviderException
は、元のデータ・ソースの読取りまたは書込み違反を検出すると、例外をスローします。SyncResolverオブジェクトを使用して、行内の各競合を調査し、解決したら、競合のある次の行へ移動して、この手順を繰り返します。
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
の解決を開始する前に、他のどのプロセスもデータを更新することのないようにします。
RowSetデータの同期における競合の種類
表8-2に、RowSetからデータベースにデータの変更を同期する際に起こる、競合状況の種類について示します。
表8-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を使って直接的に処理することはできません。 |
SyncResolverのサンプル・コード
例8-5に、RowSetとデータベースの間で競合している値をSyncResolverを使って解決する方法についての要約されたサンプルを示します。このサンプルでは、競合のあるSyncResolverの各行について、認識されている名前を持つ列の値をチェックします。このサンプルの詳細については、サンプルの後の節で説明します。
例8-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); //check for null in each column //write out the conflict //set resolved value to value in the db for this example //handle exception for deleted row in the database 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) { } }
SyncResolverオブジェクトの取得
SyncProviderException
を処理するために、例外を捕捉してそこからSyncResolver
オブジェクトを取得できます。たとえば:
try { rowset.acceptChanges(); } catch (SyncProviderException spex) { SyncResolver syncresolver = spex.getSyncResolver(); . . . }
SyncResolverは、SyncResolver
インタフェースを実装するRowSetです。SyncResolverオブジェクトには、元のRowSetのすべての行に対応する行が含まれます。競合していない値については、SyncResolverでの値はnullになります。競合している値については、データベースの現在の値が値として格納されます。
SyncResolverオブジェクト内の移動
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); . . . // compare values in the rowset and SyncResolver to determine // which should be the resolved value (the value to persist) } }
RowSetデータの同期の競合に対する解決後の値の設定
データベースに永続化するための適切な値を設定するには、setResolvedValue()
を呼び出します。たとえば:
syncresolver.setResolvedValue(i, resolvedValue);
setResolvedValue()
メソッドでは以下の変更が行われます。
-
データベースに永続化するための値を設定します。つまり、RowSetの現在の値を設定します。変更が同期されると、この新しい値がデータベースに永続化されます。
-
RowSetのデータの元の値をデータベースの現在の値に変更します。元の値とは、最後の同期以降に設定されていた値です。
setResolvedValue()
を呼び出すと、元の値がデータベースの現在の値になります。 -
同期の呼出しにおけるWHERE句を、データベースの適切な行が更新されるように変更します。
WLCachedRowSet
WLCachedRowSet
はCachedRowSets
、FilteredRowSets
、WebRowSets
、およびSortedRowSets
の拡張です。
WLCachedRowSet
には以下のような特性があります。
-
WebLogic ServerのRowSet実装では、すべてのRowSetが元は
WLCachedRowset
として作成されます。WLCachedRowSet
は、それを拡張するあらゆる標準RowSet型として互換的に使用できます。 -
WLCachedRowSet
には、RowSetをより簡単に扱えるようにする便利なメソッド群があり、またオプティミスティックな同時実行性オプションとデータ同期化オプションを設定するメソッド群も含まれています。 -
SQLXML
データ型オブジェクトを読み取ったり更新したりすることはできない場合があります。JDBC 4.0仕様では、ベンダーがSQLXML
オブジェクトを設定後にこれを読取り可能にする必要はないとしています。WebLogic ServerがSQLXML
データ型オブジェクトに値を設定したら、このオブジェクトを読み取ったり更新したりできなくなります。
Oracle WebLogic Server Java APIリファレンスインタフェースのweblogic.jdbc.rowset.WLCachedRowSet
を参照してください。
SharedRowSet
RowSetは1つのスレッドでのみ使用できます。複数のスレッドで共有することはできません。SharedRowSetはCachedRowSetを拡張したもので、元のCachedRowSetのデータに基づいて、他のスレッドで使用するCachedRowSetを追加作成できます。
SharedRowSetには以下のような特性があります。
-
各SharedRowSetは、元のRowSetの表層的なコピー(データのコピーではなく、元のRowSetのデータへの参照)で、独自のコンテキスト(カーソル、フィルタ、ソーター、保留中の変更、および同期プロバイダ)を備えています。
-
任意のSharedRowSetで行われたデータの変更がデータベースとの間で同期されると、基底のCachedRowSetも更新されます。
-
SharedRowSetを使用すると、アプリケーションで必要とするデータベースへの往復回数が減るため、パフォーマンスが向上する可能性があります。
SharedRowSetを作成するには、WLCachedRowSetインタフェースのcreateShared()
メソッドを使用して、その結果をWLCachedRowSet型にキャストします。たとえば:
WLCachedRowSet sharedrowset = (WLCachedRowSet)rowset.createShared();
SortedRowSet
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"));
Oracle WebLogic Server Java APIリファレンスの次のインタフェースおよびクラスを参照してください:
SQLPredicate、SQL方式のRowSetフィルタ
SQLPredicateクラスを使用すると、SQLのようなWHERE句の構文を使用してFilteredRowSetのフィルタを定義できます。
SQLPredicateとは
Weblogic Serverにはjavax.sql.rowset.Predicate
インタフェースの実装であるweblogic.jdbc.rowset.SQLPredicate
クラスが用意されています。SQLPredicate
クラスを使用すると、SQLのようなWHERE句の構文を使用してFilteredRowSetのフィルタを定義できます。
SQLPredicateの文法
SQLPredicateクラスの文法はJMSセレクタの文法から借用されたもので、SQL SELECT文のWHERE句の文法とよく似ています。
重要なノートを以下に示します。
-
列を参照する場合、列名を使用する必要があります。列の索引番号は使用できません。
-
この文法では、演算子および数学演算がサポートされます。たとえば次のようなことができます。
(colA + ColB) >=100.
-
WHERE句の作成では、単純なデータ型のみを使用できます。以下のようなデータ型を使用できます。
-
String
-
Int
-
Boolean
-
Float
-
-
以下のような複合データ型はサポートされません。
-
Array
-
BLOB
-
CLOB
-
Date
-
サンプル・コード
//S E T F I L T E R //use SQLPredicate class to create a SQLPredicate object, //then pass the object in the setFilter method to filter the RowSet. SQLPredicate filter = new SQLPredicate("ID >= 103"); rs.setFilter(filter);
Oracle WebLogic Server Java APIリファレンスのweblogic.jdbc.rowset.SQLPredicate
を参照してください。
オプティミスティックな同時実行性ポリシー
オプティミスティックな同時実行性の場合、RowSetは複数のユーザーが同じデータを同時に変更する可能性は低いという前提で動作します。そのため、接続されていないRowSetモデルの一端として、RowSetではデータベース・リソースがロックされません。
ほとんどの場合、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コードにどのように影響するかが示されます。
VERIFY_READ_COLUMNS
デフォルトでは、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
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
VERIFY_SELECTED_COLUMNSでは、主キー列と指定する列がWHERE句に含まれます。
WLRowSetMetaData metaData = (WLRowSetMetaData) rowSet.getMetaData(); metaData.setOptimisticPolicy(WLRowSetMetaData.VERIFY_SELECTED_COLUMNS); // Only verify the e_salary column 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
VERIFY_NONEポリシーでは、主キー列のみがWHERE句に含まれます。データベース・データに対して追加で検証は行われません。
この例では、RowSetから次のように発行されます。
UPDATE employees SET e_salary = 20000 WHERE e_id = 1
VERIFY_AUTO_VERSION_COLUMNS
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
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では一貫性が確保される程度は低下しますが、オプティミスティックな競合が起きる可能性も少なくなります。高レベルなデータの一貫性の必要性よりも、パフォーマンス、競合の回避の方が重要な場合は、これらのポリシーを検討します。
パフォーマンスのオプション
JDBCバッチ処理、グループ削除などのRowSetのパフォーマンス・オプションについて学習します。
JDBCのバッチ処理
RowSet実装ではJDBCのバッチ処理もサポートされています。各SQL文をJDBCドライバに個々に送信する代わりに、バッチ処理では文の集合を1つの一括処理としてJDBCドライバに送信します。バッチ処理はデフォルトで無効になっていますが、一般に1つのトランザクションで多数の更新が発生する場合にパフォーマンスが向上します。アプリケーションとデータベースに対してこのオプションを有効および無効に設定して、ベンチマークを実行してみるとよいでしょう。
WLCachedRowSetインタフェースには、INSERT、DELETE、およびUPDATE文のバッチ処理を制御するためのsetBatchInserts(boolean)
、setBatchDeletes(boolean)
、およびsetBatchUpdates(boolean)
メソッドがあります。
ノート:
setBatchInserts
、setBatchDeletes
、setBatchUpdates
メソッドは、acceptChanges
メソッドを呼び出す前に呼び出す必要があります。
Oracle Databaseのバッチ処理の制限
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です。