目次 | 前の項目 | 次の項目 JDBCTM ガイド: 使用の開始


5 結果セットに施された拡張

この章では、結果セットに追加された新機能について説明します。 この拡張の目的は、スクロール機能と更新機能という 2 つの基本機能を結果セットに追加することです。 結果の処理時に JDBC ドライバが高いパフォーマンスを提供できるように数種類のメソッドが追加されました。この章では、追加された新機能の説明のため、さまざまな例を用いています。

5.1     スクロール

文を実行することによって作成される結果セットは、その中を順方向 (先頭行から最終行へ) だけでなく逆方向 (最終行から先頭行へ) にも移動できる機能をサポートしています。このような機能をサポートしている結果セットを、スクロール可能な結果セットといいます。スクロール可能な結果セットでは、相対位置指定と絶対位置指定もサポートしています。絶対位置指定とは、結果セット内の行の絶対位置を指定して特定の行に直接移動する機能です。 一方、相対的位置指定とは、現在の行からの相対位置を指定して特定の行に直接移動する機能です。JDBC API における絶対位置指定と相対位置指定の定義は、X/Open の SQL CLI 仕様がモデルになっています。

5.2     結果セットのタイプ

JDBC 1.0 API では、順方向専用の 1 種類の結果セットしか提供されていませんでした。 JDBC 2.1 コア API では、 順方向専用、スクロール非反映型、スクロール反映型の 3 種類の結果セットが提供されています。名前からわかるように、新しく提供された 2 つの結果セットはスクロールをサポートしていますが、この 2 つの結果セットは開かれている間に加えられた変更が反映されるかどうかという点に違いがあります。

スクロール非反映型の結果セットでは、通常、開かれている間に加えられた変更は反映されません。スクロール非反映型の結果セットは、基盤となるデータの静的なビューを提供するだけです。スクロール非反映型の結果セットに含まれる行、その順序、および列の値は、結果セットの作成時に固定されます。

一方、スクロール反映型の結果セットでは、開かれている間に加えられた変更が反映されます。 つまり、基盤となるデータの「動的」なビューを提供します。したがって、基盤となる列の値が変更されると、スクロール反映型結果セットには、その変更内容が反映されます。スクロール反映型結果セットに含まれる行とその順序は固定することも可能です (実装で定義)。

5.3     並行処理のタイプ

結果セットの並列処理タイプは、読み取り専用と更新可能の 2 つのタイプから選ぶことができます。

読み取り専用の並行処理を使用する結果セットでは、その内容を更新することはできません。読み取り専用ロックは、同じデータ項目に対して同時にいくつでも保持できるので、読み取り専用の並行処理を使用すれば、トランザクション間で並行処理の全体レベルを上げることができます。

更新可能な結果セットは更新が可能で、このような結果セットではデータベース書き込みロックを使用して、同じデータ項目に対する複数のトランザクションからのアクセスを調整することができます。書き込みロックは 1 つのデータ項目に対して 1 つしか保持できないため、並行処理数を減らすことができます。データアクセスの衝突がまれにしか起こらないと考えられる場合は、オプティミスティック並行処理制御方式が使用できます。一般に、オプティミスティック並行処理制御では、行を値またはバージョン番号で比較することで、更新の衝突が発生したかどうかを判断します。

5.4 パフォーマンス

結果セットのデータへのアクセス効率を上げるため、JDBC 2.1 対応のドライバにはパフォーマンスに関する 2 つの指示を与えることができます。具体的には、さらに行が必要になったときにデータベースから取り出す行数と、行処理の方向 (順方向、逆方向、未知) が指定できます。これらの値は、結果セットごとにいつでも変更可能です。なお、これらのパフォーマンスに関する指示を無視するように JDBC ドライバを設定することもできます。

5.5     結果セットの作成

下に示すのは、順方向専用で読み取り専用の並行処理を使用する結果セットの作成例です。この例ではパフォーマンスに関する指示は与えていないので、パフォーマンスを上げるために何を行うかはドライバ側に任されます。この接続に対してトランザクションの遮断レベルは指定していないので、作成される結果セットでは、基盤となるデータベースのデフォルトの遮断レベルが使用されます。なお、このコードは JDBC 1.0 API を使って記述されたコードであり、JDBC 1.0 API を使用した場合と同じタイプの結果セットが作成されます。

Connection con = DriverManager.getConnection(
	"jdbc:my_subprotocol:my_subname");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
	"SELECT emp_no, salary FROM employees");

次の例では、更新可能で、かつ更新内容が反映されるスクロール可能な結果セットを作成しています。行の要求時には、一度に 25 行がデータベースから取り出されます。

Connection con = DriverManager.getConnection(
	"jdbc:my_subprotocol:my_subname");

Statement stmt = con.createStatement(
	ResultSet.TYPE_SCROLL_SENSITIVE,
	ResultSet.CONCUR_UPDATABLE);
stmt.setFetchSize(25);

ResultSet rs = stmt.executeQuery(
	"SELECT emp_no, salary FROM employees");


次の例では、上の例と同じ属性を指定して結果セットを作成していますが、結果セットの生成に PreparedStatement を使用しています。

PreparedStatement pstmt = con.prepareStatement(
	"SELECT emp_no, salary FROM employees where emp_no = ?",
	ResultSet.TYPE_SCROLL_SENSITIVE, 
	ResultSet.CONCUR_UPDATABLE);

pstmt.setFetchSize(25);
pstmt.setString(1, "100010");
ResultSet rs = pstmt.executeQuery();


JDBC ドライバがどのようなタイプの結果セットをサポートしているかは、DatabaseMetaData.supportsResultSetType() メソッドで確認できます。しかし、JDBC ドライバがサポートしていないタイプの結果セットが使用されて、Statement オブジェクト、PreparedStatement オブジェクト、CallableStatement オブジェクトの作成が要求される可能性もあります。ドライバ側は、このような場合は、その文を生成する ConnectionSQLWarning を発行するとともに、以下の規則に従って、別の結果セットタイプ値を選ぶようにすべきです。

  1. スクロール可能な結果セットの作成が要求された場合は、要求されたタイプのものと違っていても、サポートしているタイプのスクロール可能な結果セットを使用する
  2. スクロール可能な結果セットが要求されたが、スクロールをドライバがサポートしていない場合は、順方向専用の結果セットを使用する
同様に、ドライバがどのようなタイプの並行処理をサポートしているかは、DatabaseMetaData.supportsResultSetConcurrency() メソッドで確認できます。サポートしていないタイプの並行処理が要求された場合は、その文を生成する ConnectionSQLWarning を発行するとともに、別の並行処理を選ぶようにすべきです。サポートしていないタイプの結果セットとサポートしていないタイプの並行処理が同時に要求された場合は、結果セットのタイプを先に選ぶようにします。

ある場合には、JDBC ドライバ側で、ResultSet に対して文の実行時に別のタイプの結果セットまたは並行処理を選ばなければならないことがあります。たとえば、複数のテーブルにまたがった結合が行われる SELECT 文では、更新可能な ResultSet は生成できません。このような場合には、ドライバ側は、その ResultSet を生成する StatementPreparedStatement、または CallableStatementSQLWarning を発行するとともに、前述したとおり、適切なタイプの結果セットまた並行処理を選ぶようにすべきです。アプリケーション側では、ResultSet.getType() メソッドと getConcurrency() メソッドを呼び出すことで、それぞれ結果セットと並行処理の実際のタイプを知ることができます。

5.6     更新処理

並行処理のタイプとして CONCUR_UPDATABLE が指定された結果セットは更新可能です。更新可能な結果セットの行は、更新、挿入、および削除が可能です。下の例では、結果セットの先頭の行を更新しています。現在の行の各列の値は ResultSet.updateXXX() メソッドで変更しますが、このメソッドは基盤となるデータベースの更新は行いません。データベースの更新は、ResultSet.updateRow() メソッドが呼び出されると行われます。列は、名前または番号で指定できます。

rs.first();
rs.updateString(1, "100020");
rs.updateFloat("salary", 10000.0f);
rs.updateRow();

アプリケーションにより更新が行われても、updateRow() を呼び出す前にカーソルが現在の行から移動された場合は、JDBC ドライバ側でその更新を破棄しなければなりません。また、アプリケーション側では、ResultSet.cancelRowUpdates() メソッドを明示的に呼び出すことで、行に対して行なった更新を取り消すことができます。cancelRowUpdates() メソッドは、updateXXX() を呼び出したあと、updateRow() を呼び出す前に呼び出さなければなりません。 それ以外の場合は無効になります。

次に、行を削除する方法を示します。結果セットの 5 番目の行がデータベースから削除されます。

rs.absolute(5);
rs.deleteRow();

下の例では、結果セットに新しい行を挿入する方法を示しています。JDBC API では、「挿入行」という考え方が定義されています。 挿入行は、結果セットごとに割り当てられ、実際に結果セットへ挿入する前に、新しい行を作成するための準備領域として使用されるものです。結果セットのカーソルを挿入行に移動するには、ResultSet.moveToInsertRow() メソッドを使用します。挿入行の各列の値の更新には ResultSet.updateXXX() メソッドを、各列の値の取得には ResultSet.getXXX() メソッドをそれぞれ使用します。挿入行の内容は、ResultSet.moveToInsertRow() の呼び出し直後は未定義です。つまり、moveToInsertRow() を呼び出したあと、ResultSet.updateXXX() で値を設定するまでは、ResultSet.getXXX() により返される値は未定義になります。

カーソルが挿入行に置かれている間は、ResultSet.updateXXX() を呼び出しても、基盤となるデータベースや結果セットは更新されません。挿入行ですべての列の値を設定したあとに ResultSet.insertRow() を呼び出すと、結果セットと基盤となるデータベースが同時に更新されます。カーソルが挿入行に置かれている間に updateXXX() で値が与えられなかった列や、結果セットにない列には、NULL 値が設定できるようになっていなければなりません。そうでない場合は、insertRow() の呼び出し時に SQLException がスローされます。

rs.moveToInsertRow();
rs.updateString(1, "100050");
rs.updateFloat(2, 1000000.0f);
rs.insertRow();
rs.first();


カーソルが一時的に挿入行に置かれている間も、「結果セット内」の現在のカーソル位置が記憶されています。挿入行から離れるには、通常のカーソル移動メソッドのいずれかを呼び出します。 ResultSet.moveToInsertRow() を呼び出す前に現在行となっていた行にカーソルを戻す特別なメソッド ResultSet.moveToCurrentRow() を呼び出す方法もあります。上の例では、ResultSet.first() を呼び出して挿入行を離れ、結果セットの先頭行に移動しています。

データベースにより実装が異なるため、どのような SQL クエリーを使用すれば更新機能をサポートした JDBC ドライバ用に更新可能な結果セットを間違いなく得られるかは、JDBC API では規定していません。しかし、一般的には、次の条件を満たしたクエリーであれば、更新可能な結果セットを得ることができます。

  1. データベース中のテーブルを 1 つしか参照していない
  2. 結合操作を行わない
  3. 参照するテーブルの主キーを選択している
また、挿入操作を行う場合は、次の条件も満たしている必要があります。

  1. 基盤となるテーブルに含まれる列のうち、NULL に設定できない列をすべて選択する
  2. デフォルト値を持たない列をすべて選択する

5.7     カーソル移動の例

結果セットには、「カーソル」と呼ばれる内部ポインタがあります。 これは、現在アクセスしている結果セット内の行を示すものです。結果セットのカーソルは、コンピュータの画面上の現在位置を表すカーソルと同じようなものです。順方向専用の結果セットでは、カーソルは、結果セット内を順方向にのみ移動できます。したがって、先頭行から順に行がアクセスされます。

結果セットに対して順方向の繰り返し処理を行うには、JDBC 1.0 API と同じように ResultSet.next() メソッドを使用します。また、スクロール可能な結果セット (順方向専用ではない結果セット) には beforeFirst() というメソッドが実装されており、このメソッドを使用すると、結果セットの先頭行の前にカーソルを配置できます。

次の例では、カーソルを先頭行の前に配置したあと、結果セットの内容に対して順方向の繰り返し処理を行なっています。また、JDBC 1.0 API のメソッドである getXXX() メソッドを使用して、列の値を取得しています。

rs.beforeFirst();
while ( rs.next()) {
	System.out.println(rs.getString("emp_no") +
			   " " + rs.getFloat("salary"));
}

もちろん、次のようにスクロール可能な結果セットに対して逆方向の繰り返し処理を行うことも可能です。

rs.afterLast();
while (rs.previous()) {
	System.out.println(rs.getString("emp_no") +
		" " + rs.getFloat("salary"));
}

この例では、ResultSet.afterLast() メソッドでスクロール可能な結果セットのカーソルを最終行の後ろに配置しています。そのあと、ResultSet.previous() メソッドで、最終行、最終行の 1 つ前の行というようにカーソルを移動しています。ResultSet.previous() は、それ以上行が存在しない場合は false を返すので、すべての行を処理したらループは終了します。

これまでの ResultSet インタフェースの説明を見てくると、スクロール可能な結果セットの行に対して繰り返し処理を行う方法はほかにもあると思えます。しかし、次の例をよく見てみてください。 この方法は正しくありません。

// incorrect!!!
while (!rs.isAfterLast()) {
	rs.relative(1);
	System.out.println(rs.getString("emp_no") +
		 " " + rs.getFloat("salary"));
}


この例では、スクロール可能な結果セットに対して順方向の繰り返し処理を行おうとしていますが、いくつかの理由でこの方法は正しくありません。問題の 1 つは、結果セットが空の場合に ResultSet.isAfterLast() を呼び出すと、最終行が存在しないので false 値が返され、その結果、ループ本体が実行されてしまう点です。もう 1 つの問題は、結果セットにデータが含まれている場合で、結果セットの先頭行の前にカーソルが配置されているときに起こります。この状態で rs.relative(1) が呼び出されると、現在行が存在しないため、エラーになります。

次の例は、上の例での問題を解決したものです。この例では、ResultSet.first() を使用して、結果セットが空の場合とデータが含まれる場合とを区別しています。そして、結果セットが空でない場合だけ ResultSet.isAfterLast() が呼び出されるようになっているので、ループの制御にも問題はありません。 また、ResultSet.first() で最初にカーソルを先頭行に配置しているので、ResultSet.relative(1) で正常に次の行に進むことができます。

if (rs.first()) {
	while (!rs.isAfterLast()) {
		System.out.println(rs.getString("emp_no") +
			" " + rs.getFloat("salary"));
	    	rs.relative(1);
	}
}

5.8     変更内容の検出と表示

これまで、さまざまなタイプの結果セットを紹介し、いくつかのタイプの結果セットについては、その作成方法と更新方法、結果セット内を移動する方法を、例を取り上げて示しました。この項では、結果セットの各タイプの違いをさらに詳しく見ていき、それらがアプリケーションに及ぼす影響について説明します。

JDBC API で提供されている結果セットのタイプ (順方向専用、スクロール非反映型、およびスクロール反映型) によって、基盤となるデータの変更がアプリケーションから見えるかどうかという点で大きな違いがあります。特にスクロールをサポートしているタイプの結果セットでは、開かれている間、特定の行に何回も移動できるので、結果セットに変更内容が反映されるかどうかという点は興味深い問題です。

5.8.1 変更の可視性

最初に、トランザクションレベルでの変更の可視性から説明します。まず、あるトランザクションによって行われた更新は、すべてそのトランザクションから見えるというのは当然です。しかし、ほかのトランザクションによって行われた変更 (更新、挿入、および削除) があるトランザクションから見えるかどうかは、トランザクションの遮断レベルによって決まります。トランザクションの遮断レベルは、次のメソッドにより設定できます。

con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);


con は、Connection 型の変数です。システム内のすべてのトランザクションが TRANSACTION_READ_COMMITTED かそれ以上の遮断レベルで実行される場合、トランザクションからは、ほかのトランザクションがコミットした変更だけが見えます。また、結果セットが開かれているときに、その結果セットが属するトランザクションから見える変更は、その結果セット全体から常に見えます。これは、あるトランザクションによって行われた更新が別のトランザクションから見えることを表します。

しかし、結果セットが開かれている間に加えられた変更についてはどうでしょうか。そのような変更は、たとえば ResultSet.getXXX() を使用することによって結果セット全体から見えるのでしょうか。結果セットが開かれている間、結果セットには、ほかのトランザクションによって基盤となるデータに加えられた変更が反映されるか、同じトランザクションに属するほかの結果セットによって加えられた変更が反映されるか、自分自身が行なった変更が反映されるかは、結果セットのタイプによります。 なお、ほかのトランザクションと、同じトランザクションに属するほかの結果セットによって加えられた変更を、まとめて「他による変更」と呼ぶことにします。

5.8.2 他による変更

スクロール非反映型結果セットには、開かれたあとは、他 (ほかのトランザクションおよび同じトランザクション内のほかの結果セット) によって加えられた変更は反映されません。 スクロール非反映型結果セットの内容は、他によって加えられた変更については静的です。つまり、中に含まれる行と、その順序、および値は固定されています。たとえば、静的な結果セットが開かれている間、中に含まれる行がほかのトランザクションによって削除されても、結果セット上からはその行は消えません。スクロール非反映型結果セットを実装する方法としては、結果セットのデータのプライベートなコピーを作成する方法があります。

スクロール反映型結果セットは、これとは反対の性質を持ちます。スクロール反映型結果セットには、それが属するトランザクションから見えるによって加えられた更新がすべて反映されます。ただし、挿入と削除については反映されません。

更新が反映されるとはどういうことか、注意深く見ていきましょう。ほかのトランザクションによって行われた更新により特定の行があるべき場所が変更された場合、つまり、行が削除され、次に挿入された場合、その行は、結果セットが再度開かれるまで移動しません。また、更新により、ある行が結果セットの構成行でなくなった場合、つまり、行が削除された場合、その行は、結果セットが再度開かれるまで結果セット上から消えません。スクロール反映型結果セットでは、ある行がほかのトランザクションによって明示的に削除された場合、絶対的位置指定により行の論理的な取り出しが行えるように、その行の部分にプレースホルダが挿入されます。ただし、更新された列の値は常に反映されます。

DatabaseMetaData インタフェースを使用すると、結果セットがサポートしている機能を具体的に調べることができます。 たとえば、 othersUpdatesAreVisibleothersDeletesAreVisibleothersInsertsAreVisible の新しい 3 つのメソッドがこの目的に使用できます。

順方向専用の結果セットは、実際には、スクロール非反映型かスクロール反映型のどちらかの結果セットが退歩した形です。 これは、結果セットを生成するクエリーを DBMS がどう評価したかによります。クエリーの種類によりますが、ほとんどの DBMS はクエリーの結果をインクリメンタルに生成する機能を持っています。クエリーの結果がインクリメンタルに生成される場合、データ値は DBMS が要求するまで実際には取得されず、また、結果セットは反映型の結果セットのように動作します。しかし、インクリメンタルな生成ができないクエリーもあります。たとえば、結果セットがソートされている場合は、DBMS により結果セットの先頭行がアプリケーションに返される前に、事前に結果セット全体を生成しておかなければならないことがあります。このような場合は、順方向専用の結果セットはスクロール非反映型の結果セットのように動作します。

TYPE_FORWARD_ONLY の結果セットでは、othersUpdatesAreVisibleothersDeletesAreVisibleothersInsertsAreVisible の各メソッドを使用すれば、DBMS によって結果セットがインクリメンタルに生成されたときに挿入、更新、および削除が反映されるかどうかを調べることができます。クエリーの結果がソートされている場合は、これらのメソッドが true を返した場合であってもインクリメンタルな生成ができない場合があり、また、変更は反映されません。

5.8.3 結果セット自身による変更

他によって加えられた変更が結果セットに反映されるかどうかは、概して結果セットのタイプによるということを説明しました。開かれた結果セットに変更が反映されるかどうかに関して最後に検討すべき点は、結果セット自身による変更 (挿入、更新、および削除) が結果セットに反映されるかどうかということです。 結果セットによって加えられた変更が、結果セット自身に反映されるかどうかは、DatabaseMetaData のメソッド (ownUpdatesAreVisibleownDeletesAreVisible、および ownInsertsAreVisible) で調べることができます。変更が反映されるかどうかは DBMS ごと、JDBC ドライバごとに異なるため、これらのメソッドが用意されています。

updateXXX() の呼び出しのあとに getXXX() を呼び出して、更新された列の値を取得できる場合は、結果セット自身による更新が反映されていることになります。updateXXX() を呼び出したあとも getXXX() で古い列の値が返される場合は、更新は反映されていないことになります。同様に、insertRow() を呼び出したあと、挿入した行が結果セットに現れる場合は、挿入が反映されていることになります。insertRow() を呼び出したあと、結果セットを開き直さない状態で、挿入した行が結果セットに現れない場合は、挿入は反映されていないことになります。行の削除後、結果セットからその行が消えている場合、または、結果セット内に空きができている場合は、削除が反映されていることになります。

次の例では、TYPE_SCROLL_SENSITIVE の結果セットで、その結果セット自身による変更が反映されるかどうかを調べる方法を示しています。

DatabaseMetaData dmd;
...
if (dmd.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE))
{
	// changes are visible
}

5.8.4 変更の検出

結果セットが開かれてから行われた、結果セットに反映される更新、削除、または挿入により、行が影響を受けたかどうかは、ResultSet.wasUpdated()wasDeleted()wasInserted() の各メソッドを使用して調べることができます。結果セットが変更を検出できるかどうかと、変更が反映されるかどうかは別の問題です。つまり、変更が反映されても、自動的には検出されないということです。

DatabaseMetaData インタフェースには、特定のタイプの結果セットについて JDBC ドライバがその変更を検出できるかどうかを調べることができるメソッドがあります。次に例を示します。

boolean bool = dmd.deletesAreDetected(
	ResultSet.TYPE_SCROLL_SENSITIVE);

deletesAreDetected が true を返した場合は、ResultSet.wasDeleted() を使用することで、TYPE_SCROLL_SENSITIVE の結果セット内に空きができているかどうかを検出できます。

5.9     行の取り出し

アプリケーションによっては、行に対して加えられた変更のうち、最後から 2 番目までの変更を取得する必要がある場合があります。JDBC ドライバは基盤となるデータベースからデータを先読みし、キャッシュしておくことができるので (ResultSet.setFetchSize() を参照)、更新が反映される結果セットを使用している場合であっても、アプリケーションは行に対して加えられた最新の変更を取得できないことがあります。このような場合に ResultSet.refreshRow() を使用すると、データベースに格納されている最新の値で行を更新するようドライバに要求できます。JDBC ドライバは、フェッチサイズが 2 以上の場合は一度に複数の行を取り出すことがあります。 refreshRow() を頻繁に呼び出すとパフォーマンスが低下する可能性があるため、refreshRow() の呼び出しは必要最小限にしてください。

5.10     JDBC API の仕様への準拠

大部分の JDBC ドライバでスクロール可能な結果セットをサポートするのが望ましいのですが、スクロール機能をサポートしていないデータソース用の JDBC ドライバを実装する場合に、実装が簡単になるように、スクロール可能な結果セットのサポートは任意となっています。目標は、スクロール可能な結果セットをサポートしていないシステム向けに、基盤となるデータベースが提供しているサポートを使用して、JDBC ドライバがスクロール可能な結果セットを実装できるようにすることです。対応する DBMS がスクロール機能をサポートしていない場合は、JDBC ドライバにスクロール機能を実装しなくてもかまいませんし、DBMS の一番上の層としてスクロール機能を実装してもかまいません。なお、JDBC オプションパッケージ API の一部である JDBC 行セットは、必ずスクロール機能をサポートしています。 したがって、基盤となる DBMS がスクロール可能な結果セットをサポートしていない場合は、行セットを使うことができます。



目次 | 前の項目 | 次の項目
jdbc@eng.sun.com または jdbc-business@eng.sun.com
Copyright © 1996-1999 Sun Microsystems, Inc. All rights reserved.