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

ResultSet

注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいています。

5.1 ResultSet の概要

ResultSet は、SQL クエリーの実行結果を含む Java オブジェクトです。 つまり、ResultSet にはクエリーの条件を満たす行が含まれます。 ResultSet オブジェクトに格納されたデータは、現在行のさまざまな列へのアクセスを可能とする、get メソッドセットを使って取得されます。 ResultSet.next メソッドは、ResultSet の次の行に移動するために使用され、次の行が現在行になるようにします。

ResultSet の一般的な形式は、列の見出しとクエリーの結果に対応する値から成るテーブルです。 たとえば、クエリーが SELECT a, b, c FROM Table1 であれば、結果は次のような形式になります。

	
a           b               c
----------  ------------    ----------- 
12345       Cupertino       2459723.495
83472       Redmond         1.0
83492       Boston          35069473.43

次のコードは、列 aint、列 bString、そして列 cfloat とする行のコレクションを戻す SQL 文の実行例です。

java.sql.Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
	// retrieve and print the values for the current row
	int i = rs.getInt("a");
	String s = rs.getString("b");
	float f = rs.getFloat("c");
	System.out.println("ROW = " + i + " " + s + " " + f);
}

5.1.1 行と列

リレーショナルデータベースは、複数のテーブルで構成され、各テーブルは行と列で構成されます。 リレーショナルデータベースのテーブルの中の行は、テーブルが表すエンティティのインスタンスを表していると見なすことができます。 たとえば、従業員テーブルが存在する場合、各行には特定の従業員に関する情報が含まれます。 従業員に関する各断片データは列に格納されます。このため、たとえば、従業員テーブルには、ID 番号、名前、給与、および採用日付を格納する列が含まれます。 ある行の内部の列には、特定の従業員の ID 番号、名前、給与、および採用日付が含まれます。

結果セットも行および列を含むテーブルですが、結果セットには、クエリーの条件を満たすデータベーステーブルから取得した列値だけが含まれます。 言い換えると、結果セット行には、基になるデータベーステーブル内の列のサブセットが含まれます (ただし、クエリーによりテーブル内のすべてが選択される場合を除きます。その場合には、結果セットテーブルには、データベーステーブル内のすべての行のすべての列値が含まれます)。過去において、リレーショナルデータベーステーブル (つまり結果セットテーブル) の列値は、不可分 (atomic) な値、つまり 1 つの分割不可能な値でなければなりませんでした。 たとえば、配列は複数の要素で構成されるため、配列を列値とすることはできませんでした。 しかし、SQL3 データ型の出現によって、テーブル列に含めることのできる内容は劇的に拡張されました。 現在では、配列やユーザ定義の構造化型でさえも列値にできます。 この新たな機能により、リレーショナルデータベースは複合型のインスタンスを列値として格納できるようになりました。その結果、リレーショナルデータベースはオブジェクトデータベースにより近いものとなり、リレーショナルデータベースとオブジェクトデータベースとの境界はあいまいになりつつあります。 プログラマは、SQL3 型をサポートした JDBC 2.0 ドライバを使用することにより、これらの新しいデータ型を活用できます。

5.1.2 カーソル

ResultSet オブジェクトは、現在行のデータを指し示すカーソルを維持します。 カーソルは next メソッドが呼び出されるたびに 1 行ずつ下に移動します。 ResultSet の最初の作成時に、カーソルが最初の行の前に配置されます。このため、next メソッドの最初の呼び出しにより、カーソルは最初の行に移動して、そこが現在行になります。 ResultSet の行は、最上行から順次下に取得され、次の next メソッドの呼び出しのたびにカーソルが 1 行ずつ下に移動します。 カーソルが順方向にだけ移動するというこの能力は、ResultSet のデフォルト動作で、JDBC 1.0 API だけを実装するドライバはこの動作のみ可能です。 この種の結果セットは、ResultSet.TYPE_FORWARD_ONLY 型を持ち、順方向専用の結果セットと呼ばれます。

ドライバがカーソル移動メソッドを JDBC 2.0 コア API で実装している場合、その結果セットはスクロール可能になります。 スクロール可能な結果セットのカーソルは、特定の行への移動に加え、順方向にも逆方向にも移動可能です。 メソッド previousfirstlastabsoluterelativeafterLast、および beforeFirst は、それぞれ、カーソルを現在行から逆方向に、最初の行、最後の行、特定の行番号等に移動します。 結果セットをスクロール可能にする方法およびその実例については、「タイプの異なる結果セットの作成」で説明します。

カーソルが ResultSet オブジェクト内のある行 (先頭行の前や最終行の後ではなく) に位置する場合、その行が現在行となります。 つまり、カーソルがその行に位置している間に呼び出されたメソッドは、(1) その行の値に基づいて処理を行うか (getXXXupdateXXX などのメソッド)、(2) その行全体に対して処理を行うか (updateRowinsertRowdeleteRowrefresh-Row などのメソッド)、または (3) その行を他の行に移動するための始点として使用します (relative などのメソッド)。

カーソルは、ResultSet オブジェクトまたはその上位の Statement オブジェクトが閉じられるまで有効になっています。

5.1.3 カーソル移動の例

前のセクションで説明したように、順方向のみの結果セットでは、標準的なカーソル移動は、next メソッドを使って、結果セットの各行を 1 回のみ最初から最後まで順番に移動するものです。 スクロール可能な結果セットでは、ある行に戻ったり、結果セットに対して何回でも繰り返して処理を行うことが可能です。 これが可能なのは、カーソルを先頭行の前にいつでも移動できるためです (beforeFirst method (ResultSet interface)>beforeFirst メソッドを使用) (ResultSet インタフェースの beforeFirst メソッドを使用)。 カーソルは結果セット全体の処理を next メソッドを使用してもう一度開始できます。 次の例では、カーソルを先頭行の前に配置したあと、結果セットの内容に対して順方向の繰り返し処理を行なっています。 処理する行がなくなるまで、getString メソッドおよび getFloat メソッドは、各行の列値を取得します。処理する行がなくなると、next メソッドは値 false を返します。

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

次の例に示すように、結果セットの処理を逆方向に繰り返すこともできます。 カーソルは、まず結果セットの終端に移動します (afterLast メソッドを使用)。次に while ループ内で previous メソッドが呼び出され、繰り返しのたびに前の行に移動して、結果セットの内容を処理します。 previous メソッドは、それ以上行が存在しない場合は false を返すので、すべての行を処理したらループは終了します。

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

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) が呼び出されると、現在行が存在しないため、エラーになります。 relative メソッドは、カーソルを現在行から指定された番号の行へ移動します。このメソッド呼び出しは、カーソルが現在行にある場合にのみ実行可能です。

次のコードは、上の例の問題を解決したものです。 この例では、ResultSet.first メソッドを使用して、結果セットが空の場合とデータが含まれる場合とを区別しています。 ResultSet.isAfterLast は、結果セットが空でない場合にだけ呼び出されるため、ループ制御は正しく動作します。 ResultSet.first method (ResultSet interface)>first ResultSet インタフェースの beforeFirst メソッドは、カーソルを最初の行に配置するため、ResultSet.relative(1) メソッドは意図したとおりに各行を処理します。

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

5.1.4 結果セット内の行数の判定

新しいカーソル移動関連のメソッドを使用すると、スクロール可能な ResultSet オブジェクトに含まれる行数を容易に知ることができます。 方法は簡単で、結果セットの最終行に移動し、その行番号を取得するだけです。 次の例では、rs は従業員ごとに 1 つの行を保持します。

ResultSet rs = stmt.executeQuery(
		"SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES");
rs.last();
int numberOfRows = rs.getRow();
System.out.println("XYZ, Inc. has " + numberOfRows + " employees");
rs.beforeFirst();
while (next()) {
	. . . // retrieve first and last names of each employee
}

上の例ほど簡単ではありませんが、スクロール不可能な結果セットに含まれる行数を知ることもできます。 次の例は、行数を知る 1 つの方法を示します。

ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM EMPLOYEES");
rs.next();
int count = rs.getInt(1);
System.out.println("XYZ, Inc. has " + count + " employees");

ResultSet rs2 = stmt.executeQuery(
		"SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES");
while (rs2.next()) {
	. . . // retrieve first and last names of each employee
}

スクロール可能な結果セットの場合、同じ結果セットへの繰り返し処理を開始しそのデータを取得するには、単にカーソルを再度位置付けするだけです。 ただし、前の例では、カウントを取得するためにクエリーを 1 つ必要とし、目的のデータを保持する結果セットを取得するために別のクエリーを必要とします。 もちろん、カウントが正確であるためには、両方のクエリーが同一サイズの結果セットを取得する必要があります。

順方向のみの結果セットで行数を調べる別の方法は、次の例に示すように、結果セット内で処理を繰り返すたびに変数の値を増加させることです。 順方向のみの結果セットの場合、アプリケーションは、繰り返し処理を 1 回だけしか実行できないため、同じクエリーを 2 度実行する必要があります。 この例では、最初の rs に対する繰り返しによって行数がカウントされ、2 回目の rs に対する繰り返しによってデータを取得しています。

ResultSet rs = stmt.executeQuery(
		"SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES");
int count = 0;
while (rs.next()) {
	count++;
}
System.out.println("Company XYZ has " + count " employees.");
rs = stmt.executeQuery(
		"SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES");
while (rs.next()) {
	. . . // retrieve first and last names of each employee
}

5.1.5 列値の取得

ResultSet.getXXX メソッドは、現在行から列値を取得する手段を提供します。 順方向のみの結果セットで移植性を最大にするためには、値を左から右に 1 回のみ取得し、かつ列値を読み取り専用にします。 スクロール可能な結果セットでは、このような制限はありません。

どの列からデータを取り出り始めるかを指定するには、列名または列番号のどちらかが使用できます。 たとえば、ResultSet オブジェクト rs の 2 列目が TITLE という名前で、値を文字列として格納する場合は、次のどちらでもその列に格納された値を取り出します。

String s = rs.getString(2);
String s = rs.getString("TITLE");

列には、左から右へと列 1 から始まる番号が付けられていることに注意してください。また、getXXX メソッドへの入力に使用される列名は大文字小文字が区別されません。

クエリーで列名を指定したユーザが getXXX メソッドの引数として同じ名前を使用できるように、列名を使用するオプションが提供されました。 一方、SELECT 文に列名が指定されていない場合 ("SELECT * FROM TABLE1" であるか、または列が導き出された列である場合など) には、列番号を使用すべきです。 そのような場合は、ユーザが列名を確実に知る方法はありません。

場合によっては、SQL クエリーの結果が同名の列を複数持つ結果のセットを返すこともあり得ます。 getXXX メソッドを指すパラメータとして、列名が使用されている場合には、getXXX は最初に一致する列名の値を返します。 したがって、同名の列が複数ある場合は、正しい列値を取り出したことを確認するために列の添字を使用する必要があります。 また、列番号を使用した方が多少効率的であると言えます。

列の名前が既知であるがその添字がわからない場合は、メソッド findColumn を使用して列番号を検索することができます。

ResultSet.getMetaData メソッドを呼び出すと、ResultSet の中の列に関する情報を入手できます。返される ResultSetMetaData オブジェクトから、その ResultSet オブジェクトの列の番号、型、およびプロパティが得られます。

5.1.6 どの getXXX メソッドを使用するか

JDBC ドライバは、強制型変換をサポートします。 getXXX メソッドが呼び出されると、ドライバは基になるデータを Java プログラミング言語の XXX 型に変換しようと試みてから、適切な値を返します。 たとえば、getXXX メソッドが getString であり、基盤になっているデータベースのデータの型が VARCHAR である場合、JDBC に準拠したドライバは VARCHAR 値を Java プログラミング言語の String オブジェクトに変換します。 String オブジェクトは、getString により返される値になります。

JDBC 2.0 API は、新しい SQL3 データ型の取得用に、ResultSet.getXXX メソッドを新たに追加します。 これらのメソッドは、JDBC 1.0 API で getXXX メソッドが果たすのと同じ役割を果たします。つまり、SQL3 JDBC 型を Java プログラミング言語の型にマッピングして、その型を返します。 たとえば、getClob メソッドはデータベースから JDBC CLOB 値を取得して、java.sql.Clob インタフェースのインスタンスである Clob オブジェクトを返します。

getObject メソッドは、すべてのデータ型を取得します。 この Object はもっとも一般的な型で、Java プログラミング言語の他のすべてのオブジェクト型はこの型から生成されているので可能となります。 これは、基となるデータ型がデータベース固有の型であるか、一般的なアプリケーションがすべてのデータ型を受け入れる必要がある場合に特に有用です。 getObject メソッド は、その名前から予想されるとおり、型を限定して使用するためにナロー変換が必要な Java Object を返します。 言い換えると、派生型として使用するには、その前に一般的な Object 型から派生型へキャストする必要があります。 次のコードは、getObject メソッドを使って、ResultSet オブジェクト rs の現在行の ADDRESS 列から Struct 値を取得する方法を示します。 getObject が返す Object は、Struct にナロー変換されてから、変数アドレスに代入されます。

Struct address = (Struct)rs.getObject("ADDRESS");

getObject メソッドは、任意のデータ型から値を取得できるだけではなく、カスタムマッピングを行う唯一の ResultSet.getXXX メソッドでもあります。 このため、カスタムマッピングを行うには、データ型は getObject メソッドを使って取得する必要があります。 カスタムマッピングの可能な 2 つの SQL データ型は共にユーザ定義型であり、それらは SQL 構造化型および DISTINCT 型 です。 JDBC DISTINCT の値は、通常、その基になっている型に適した getXXX メソッドを使って取得されます。ただし、この値がカスタムマッピングを保持する場合には、カスタムマッピングのために getObject メソッドを使って取得する必要があります。 JDBC STRUCT は、getObject メソッドでのみ取得可能です。その理由は、getObject メソッドを使用すると、JDBC STRUCT 値のカスタムマッピングが存在する場合に、その使用を保証できるからです。

「x」は、指定された JDBC 型の取得に、その getXXX メソッドを使用できる可能性があることを示します。

X」は、指定された JDBC 型の取得に、その getXXX メソッドが推奨されていることを示します。

5.1.7 結果セットの型

結果セットは、さまざまなレベルの機能を持ちます。 たとえば、結果セットはスクロール可能またはスクロール不可能です。 スクロール可能な結果セットは、順方向および逆方向への移動に加え、特定の行への移動も可能なカーソルを持っています。 また、結果セットが開かれている間に行われた変更を反映するかどうかも設定できます。つまり、データベース内で修正された列値への変更を反映するかどうかを決定できます。 開発者は、ResultSet オブジェクトに機能を追加すると、オーバーヘッドが増加することを常に念頭に置く必要があります。このため、必要な場合だけ使用するようにしてください。

スクロール機能および変更の反映方法により、JDBC 2.0 コア API で利用可能な結果セットには 3 つのタイプがあります。 次の定数は、ResultSet インタフェースで定義され、これら 3 つの結果セットの指定に使用されます。

  1. TYPE_FORWARD_ONLY
  2. TYPE_SCROLL_INSENSITIVE
  3. TYPE_SCROLL_SENSITIVE

5.1.8 並行処理のタイプ

結果セットは、タイプの異なる更新機能を複数持つことができます。 スクロール機能に関しては、ResultSet オブジェクトを更新可能にするとオーバーヘッドが増えるため、必要な場合にだけ使用する必要があります。 つまり、利便性のより高い方法は、更新をプログラム的に実行することであり、これは結果セットが更新可能な場合にのみ行うことができます。 JDBC 2.0 コア API は、2 つの更新機能を提供します。各機能は、ResultSet インタフェース内の次の定数で指定されます。

  1. CONCUR_READ_ONLY
  2. CONCUR_UPDATABLE

より高度な並行処理を実現するために、更新可能な結果セットがオプティミスティック並行処理制御方式を使用するように実装することもできます。 この実装では、競合がまれにしか起こらないこと、および競合が発生した場合には書き込み専用ロックを使用して回避するという前提のもとに、より多くのユーザがデータへ同時にアクセスすることを許可します。 いかなる更新を実行するよりも前に、行を値またはバージョン番号で比較することにより、競合が発生したかどうかをチェックします。 2 つのトランザクション間で更新時に競合が発生した場合は、どちらかのトランザクションが中止されて一貫性が維持されます。 オプティミスティック並行処理制御実装は、並行性を向上させることができます。ただし、競合が多すぎるとパフォーマンスが実質的に低下します。

5.1.9 パフォーマンスヒントの提供

多くの DBMS およびドライバは、さまざまな状況で最良のパフォーマンスを提供できるよう最適化されています。このため、一般に、データベースプログラマはデフォルトの設定を使用することが推奨されています。 ただし、特定のアプリケーションに対してデータベースのパフォーマンスをチューニングする場合には、JDBC 2.0 API は結果セットデータへのアクセス効率を向上させるヒントをドライバに与えるメソッドを提供します。 これらのパフォーマンスヒントはあくまでもヒントであるため、JDBC 準拠のドライバはこれらを無視することもできます。

次の 2 つのヒントは、ドライバに対し、パフォーマンスを向上させるための指針を与えます。

  1. 新しい行が必要とされるたびにデータベースからフェッチされる行の数。 取得される行数は、「フェッチサイズ」と呼ばれ、Statement.setFetchSize および ResultSet.setFetchSize という 2 つの異なるメソッドを使用して設定できます。ResultSet オブジェクトを生成する文は、ResultSet オブジェクトのデフォルトフェッチサイズを StatementsetFetchSize メソッドを使用して設定します。 次のコードは、ResultSet オブジェクト rs のフェッチサイズを 25 に設定します。フェッチサイズが変更されるまで、Statement オブジェクト stmt により作成されたすべての結果セットは、自動的にフェッチサイズが 25 になります。
    Statement stmt = con.createStatement();
    stmt.setFetchSize(25);
    ResultSet rs = stmt.executeQuery(SELECT * FROM EMPLOYEES);
    
    結果セットのデフォルトフェッチサイズは、setFetchSize メソッドの ResultSet バージョンを使用して新たなフェッチサイズを設定することにより、変更可能です。 次のコード行は、前のコードからの継続で、rs のフェッチサイズを 50 に変更します。
    rs.setFetchSize(50);
    
    通常、ドライバに適したもっとも効率的なフェッチサイズがデフォルト値として設定されています。 setFetchSize メソッドは、特定のフェッチサイズが特定のアプリケーションに対してデフォルト値よりも効果的かどうかを簡単に試すための機能を、プログラマに対して提供します。

  2. 行が処理される方向

    ResultSet インタフェースは、行の処理方向を指定する、FETCH_FORWARDFETCH_REVERSEFETCH_UNKNOWN という 3 つの定数を定義しています。 フェッチサイズと同様、フェッチ方向を設定する 2 つのメソッドが存在します。1 つは Statement インタフェースのメソッドであり、もう 1 つは ResultSet インタフェースのメソッドです。 結果セットを作成する文は、StatementsetFetchDirection メソッドを使用してデフォルトのフェッチ方向を判別します。 次のコードは、下から上に行の処理が行われるように ResultSet オブジェクト rs のフェッチ方向を設定します。 フェッチ方向が変更されるまで、オブジェクト stmt により作成されるすべての結果セットのフェッチ方向は、自動的に逆方向になります。

    Statement stmt = con.createStatement();
    stmt.setFetchDirection(FETCH_REVERSE);
    ResultSet rs = stmt.executeQuery(SELECT * FROM EMPLOYEES);
    
    結果セットのデフォルトフェッチ方向は、ResultSetsetFetchDirection メソッドを使用することにより、いつでも変更可能です。 次のコード行は、前のコードからの継続で、rs のフェッチ方向を順方向に変更します。
    rs.setFetchDirection(FETCH_FORWARD);
    
    ResultSet オブジェクト rs は、ドライバが行を順方向に処理するというヒントを提供します。 このヒントは、ResultSet.setFetchDirection メソッドが rs に対して再度呼び出されて、示唆されたフェッチ方向が変更されるまで有効です。

    フェッチサイズと同様、ドライバは一般にもっとも効率的なフェッチ方向を使用するよう最適化されているため、デフォルト値を変更すると最適化された設定に反して動作してしまうことがあります。 setFetchDirection メソッドは、パフォーマンスを向上させる目的でアプリケーションのチューニングを簡単に試す機能をブログラマに提供します。

5.1.10 タイプの異なる結果セットの作成

結果セットは、クエリーを実行することで作成されます。結果セットのタイプは ConnectioncreateStatement メソッド (または prepareStatementprepareCall) に渡される引数によって異なります。 JDBC 1.0 API だけを使用している次のコードでは、createStatement メソッドに引数を提供していないため、デフォルトの ResultSet オブジェクト (順方向のみで、読み取り専用を並行処理する) を生成します。

Connection con = DriverManager.getConnection(
		"jdbc:my_subprotocol:my_subname");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
	"SELECT EMP_NO, SALARY FROM EMPLOYEES");

変数 rs は、EMPLOYEES テーブルの各行から取得した EMP_NO および SALARY 列の値を含む ResultSet オブジェクトを表します。 この結果セットはスクロール不可であるため、next メソッドを使って、結果セットの行全体に渡ってカーソルを上から下に移動することだけが可能です。 ResultSet オブジェクト rs は、更新できません。パフォーマンス上のヒントは何も与えられないため、ドライバは最良のパフォーマンスを得られると考えられることであれば何でも実行できます。 トランザクションの遮断レベルも設定されないため、rs は基盤となるデータベースのデフォルトのトランザクション遮断レベルを使用します。 トランザクション遮断レベルについての詳細は、「トランザクションの遮断レベル」を参照してください。

次に示す例では、新しい JDBC 2.0 コア API を使用して、スクロール可能な結果セットを作成しています。これは、更新を検知し (ResultSet.TYPE_SCROLL_SENSITIVE の指定)、かつ更新可能な (ResultSet.CONCUR_UPDATABLE の指定) 結果セットです。

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

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

ResultSet rs2 = stmt.executeQuery(
		"SELECT EMP_NO, SALARY FROM EMPLOYEES");

変数 rs2 には、前の例の rs と同じ値が含まれます。ただし、rs とは異なり、rs2 はスクロール可能、更新可能で、かつ基となるテーブルのデータ変更を検出できます。 また、新たな行が必要とされるたびにドライバがデータベースから 25 行をフェッチするようにヒントを与えます。 Statement オブジェクト stmt が実行されるたびに、スクロール可能、更新可能、データの変更を検出可能で、フェッチサイズが 25 の結果セットが作成されます。結果セットはそのフェッチサイズを変更できますが、型と並行性を変更することはできません。

既に説明したとおり、結果セットをスクロール可能または更新可能にすることには代償が伴います。このため、これらの機能を持つ結果セットは、必要な場合にだけ作成することをお勧めします。

5.1.11 PreparedStatement を使用した結果セットの作成

PreparedStatement および CallableStatement オブジェクトは、Statement インタフェースで定義されたメソッドを継承するため、いくつかの異なるタイプの ResultSet オブジェクトを生成できます。

次のコードでは、Statement オブジェクトの代わりに PreparedStatement オブジェクトを使用して、結果セットを作成します。 結果セットは、トランザクション遮断レベルが接続用に設定されることを除き、前述の例と同じ属性を持ちます。

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

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, "1000010");

ResultSet rs3 = pstmt.executeQuery();

変数 rs3 には、EMP_NO の値が 1000010 である行の EMP_NO および SALARY 列の値が含まれます。ResultSet オブジェクト rs3 は、スクロール可能、更新可能、およびデータの変更を検出可能であるという点、またドライバが一度にデータベースから 25 行をフェッチすることをヒントとして与える点で rs2 と同様です。 一方、接続がダーティ読み取り (コミットされる前に値を読み取ること) が行われないように指定を行う点では rs3 は rs2 と異なります。 rs2 に対してはトランザクション遮断レベルは設定されないため、基盤となるデータベースの遮断レベルがデフォルトで設定されます。

5.1.12 サポートされない機能の要求

JDBC 2.0 API での新機能の追加により、DBMS またはドライバがサポートしない機能をアプリケーションが要求できるようになりました。 たとえば、ドライバがスクロール可能な結果セットをサポートしない場合、順方向のみの結果セットを返すことができます。 また、クエリーによっては更新不可能な結果セットを返すものがあります。この場合、更新可能な結果セットを要求してもこれらのクエリーには何の影響もありません。 一般的な規則として、クエリーが選択する列の 1 つに主キーを含むこと、およびクエリーを 1 つのテーブルだけを参照するようにすることを念頭に置いてください。

JDBC 2.0 API の新規メソッドを使用すると、アプリケーションは、ドライバがサポートする結果セット機能を識別できます。 ある機能がサポートされているかどうかが疑わしい場合、その機能を要求する前に次のメソッドを呼び出すことをお勧めします。 次の DatabaseMetaData のメソッドは、ドライバが指定された結果セットのタイプまたは指定された結果セットの並行処理をサポートするかどうかを示します。

次の ResultSet メソッドは、メソッドの呼び出し先である特定の結果セットに対する結果セットタイプおよび結果セットの並行性を返します。

アプリケーションがスクロール可能な結果セットを指定し、かつドライバがスクロールをサポートしない場合、ドライバは文を作成した Connection オブジェクトに対して警告を発行して、順方向のみの結果セットを返します。 たとえドライバがスクロール可能な結果セットをサポートするとしても、アプリケーションは、ドライバがサポートしないスクロール可能タイプを要求する可能性があります。 その場合、ドライバは文を作成した Connection オブジェクトに対して SQLWarning を発行して、サポートするタイプのスクロール可能結果セットを (要求されたタイプと正確に一致しない場合でも) 返します。 たとえば、アプリケーションが TYPE_SCROLL_SENSITIVE 結果セットを要求したがドライバがそのタイプをサポートしない場合、ドライバは TYPE_SCROLL_INSENSITIVE 結果セットをサポートするならば、それを返します。 ドライバは、未サポートの結果セットタイプを要求する文を生成した Connection オブジェクトに対して SQLWarning を発行することにより、アプリケーションに対して、要求されたものと正確に一致するタイプを返さないことを警告します。

同様に、アプリケーションが更新可能な結果セットを指定すると、更新可能な結果セットをサポートしないドライバは、文を生成した Connection オブジェクトに対して SQLWarning を発行して、読み取り専用の結果セットを返します。 アプリケーションが未サポートの結果セットタイプおよび未サポートの並行処理タイプを要求する場合、ドライバは結果セットタイプの方を最初に選択します。

ある場合には、文の実行時に、ドライバ側で別のタイプの結果セットまたは並行処理を選ばなければならないことがあります。 たとえば、複数のテーブルにまたがった結合が行われる SELECT 文では、更新不可能な結果セットが生成される場合があります。 その場合、ドライバは、Connection オブジェクトに対して警告を発行する代わりに結果セットを作成しようとした StatementPreparedStatement、または CallableStatement オブジェクトに対して SQLWarning を発行します。 次に、ドライバは、上記の 2 つの段落に示した指針に従って、適切な結果セットタイプと並行処理タイプのいずれかまたは両方を選択します。

5.1.13 updateXXX メソッドの使用

ResultSet オブジェクトは、その並行処理タイプが CONCUR_UPDATABLE の場合、プログラム的に更新 (行の変更、挿入、または削除) が可能です。 JDBC 2.0 API は updateXXX メソッドおよび他のさまざまなメソッドを ResultSet インタフェースに追加しているので、ResultSet オブジェクトとデータベースの両方で行をプログラム的に更新できます。

新規の updateXXX メソッドは、SQL コマンドを使わずに結果セット内の値を更新可能にします。 データ型ごとに updateXXX メソッドが存在します。getXXX および setXXX メソッドの場合と同様、XXX には Java プログラミング言語のデータ型が当てはまります。 setXXX メソッドの場合と同様、ドライバは、データベースに送信する前にこのデータ型を SQL データ型に変換します。 このため、たとえば、updateBoolean メソッドは JDBC BIT 値をデータベースに送信し、updateCharacterStream メソッドは JDBC LONGVARCHAR 値をデータベースに送信します。

updateXXX メソッドは、2 つのパラメータを取ります。最初のパラメータは更新対象の列を示し、2 番目のパラメータは指定された列に割り当てる値を提供します。 getXXX メソッドの場合と同様、列は、その名前またはインデックスを使って指定できます。 アプリケーションが列名を使って結果セットから値を取得した場合、値を更新する場合にも一般に列名を使用します。 同様に、getXXX メソッドに列インデックスを指定して値を取得した場合、対応する updateXXX メソッドは値を更新する際に一般に列インデックスを使用します。

ResultSet メソッドとともに使用する列インデックスは、データベーステーブル内の列番号ではなく、結果セット内の列番号 (両者は異なる場合が多い) を参照することに留意してください。(列番号は、テーブルの列すべてが選択されている場合にのみ同一になります。) 結果セットテーブルとデータベーステーブルのどちらの場合でも、最初の列のインデックスは 1、2 番目の列のインデックスは 2 という具合になります。

次のコードでは、ResultSet オブジェクト rs の 3 番目の列値は、getInt メソッドを使って取得されます。この列値を int88 で更新するために updateInt が使用されています。

int n = rs.getInt(3); // n contains the value in column 3 of rs
. . .
rs.updateInt(3, 88); // the value in column 3 of rs is set to 88
int n = rs.getInt(3); // n = 88

3 番目の列の名前が SCORES の場合、次のコード行も ResultSet オブジェクト rs の第 3 列に int88 を割り当てることにより更新します。

int n = rs.getInt("SCORES");
. . .
rs.updateInt("SCORES", 88);

updateXXX メソッドは、結果セットの現在行の値を更新します。ただし、基盤となるデータベーステーブルの値は更新しません。 データベースの更新を行うのは、updateRow メソッドです。 カーソルが現在行 (更新対象の行) にある間に updateRow メソッドを呼び出すことが重要です。 実際のところ、アプリケーションが updateRow を呼び出す前にカーソルを移動すると、ドライバは更新内容を破棄しなければならず、そして結果セットもデータベースも更新されません。

アプリケーションは、cancelRowUpdates メソッドを呼び出すことにより、明示的に行の更新を取り消せ ます。 このメソッドは、updateXXX メソッドを呼び出した後、かつ updateRow メソッドを呼び出す前に呼び出さないと有効になりません。 他のどのような場合で cancelRowUpdates が呼び出されても、効果はありません。

次の例では、ResultSet オブジェクト rs の第 4 行の 2 番目および 3 番目の列が更新されます。 更新は現在行に対して行われるため、カーソルをまず更新対象の行 (この場合は第 4 行) に移動します。 次に updateString メソッドが呼び出されて、rs の第 2 列の値を 321 Kasten に変更します。 updateFloat メソッドは rs の第 3 列の値を 10101.0 に変更します。 最後に、updateRow メソッドが呼び出されて、変更された 2 つの列値を含むデータベース内の行を更新します。

rs.absolute(4);
rs.updateString(2, "321 Kasten");
rs.updateFloat(3, 10101.0f);
rs.updateRow();

第 2 列の名前が ADDRESS で、第 3 列の名前が AMOUNT の場合、次のコードは前の例とまったく同じ動作をします。

rs.absolute(4);
rs.updateString("ADDRESS", "321 Kasten");
rs.updateFloat("AMOUNT", 10101.0f);
rs.updateRow();

プログラム的に更新を行うことに加え、JDBC 2.0 コア API はバッチ更新を送信する機能も提供します。 バッチ更新機能は、Statement オブジェクト内で実行されます。詳細は、「バッチ更新の送信」を参照してください。

5.1.14 行の削除

JDBC 2.0 API は、ResultSet オブジェクト内の行を Java プログラミング言語のメソッドだけを使用して削除することができるように、deleteRow メソッドを提供します。 このメソッドは現在行を削除します。このため、deleteRow を呼び出す前に、アプリケーションはカーソルを削除する行に配置する必要があります。 結果セット内のある行にだけ有効な updateXXX メソッドとは異なり、このメソッドは結果セット内の現在行とデータベース内の基となる行の両方に対して効果があります。 次の 2 つのコード行は、ResultSet オブジェクト rs の先頭行を削除し、かつその基となる行 (データベーステーブルの先頭行かもしれないし、そうでないかもしれない) をデータベースから削除します。

rs.first();
rs.deleteRow();

5.1.15 行の挿入

JDBC 2.0 コア API で新たに追加されたメソッドを使って、新規の行を結果セットテーブルおよび基となるデータベーステーブルに挿入できます。 これを可能にするために、API は「挿入行」という概念を定義しています。 これは、結果セットの一部ではなく、結果セットに関連付けられた特別な行で、新しい行を作成するための準備領域として使用されるものです。 挿入行にアクセスには、アプリケーションは ResultSetmoveToInsertRow メソッドを呼び出してカーソルを挿入行に配置します。 次に、適切な updateXXX メソッドを呼び出して、列の値を挿入行に追加します。 挿入する行の列すべての設定が完了した時点で、アプリケーションは insertRow メソッドを呼び出します。 このメソッドは挿入行を、結果セットと基となるデータベースの両方に同時に追加します。 最後に、アプリケーションはカーソルを結果セット内の行に戻す必要があります。

次のコードは、Java プログラミング言語で記述されたアプリケーションから行の挿入を実行するための 3 つのステップを示します。

rs.moveToInsertRow();
rs.updateObject(1, myArray);
rs.updateInt(2, 3857);
rs.updateString(3, "Mysteries");
rs.insertRow();
rs.first();

いくつかの詳細な点に留意してください。 まず、ResultSet.getXXX メソッドを使って挿入行から値を取得できます。ただし、updateXXX メソッドを使って値が挿入行に割り当てられるまで、その内容は未定義です。 このため、moveToInsertRow メソッドの呼び出し後で updateXXX メソッドの呼び出し前に getXXX メソッドが呼び出された場合、未定義の値が返されます。

次に、挿入行に対して updateXXX メソッドを呼び出すことと、ResultSet オブジェクト内の行に対してこのメソッドを呼び出すこととは異なります。 カーソルが結果セット内の行にある場合、updateXXX メソッドへの呼び出しは結果セット内の値を変更します。 カーソルが挿入行上にある場合、updateXXX メソッドへの呼び出しは挿入行内の値を更新しますが、結果セットには何も行いません。 どちらの場合も、updateXXX メソッドは基となるデータベースには一切影響を与えません。

第 3 に、insertRow メソッドの呼び出しは、結果セットとデータベースの両方に挿入行を追加しますが、挿入行の列数がデータベーステーブルの列数と一致しない場合、SQLException をスローします。 たとえば、updateXXX メソッドを呼び出していなくて、列に値が指定されていない場合には、列が null 値を許可しない限り SQLException がスローされます。 また、結果セットに含まれていない列がテーブル上に存在している場合も、その不足している列が null 値を許可しない限り SQLException がスローされます。

4 番目に、カーソルが挿入行内に移動するときに、結果セットはカーソルがどの位置にあったかを追跡します。 その結果、ResultSet.moveToCurrentRow メソッドへの呼び出しは、moveToInsertRow メソッドが呼び出される直前に現在行であった行にカーソルを戻します。 現在行との相対位置を使用するメソッドを含む、他のカーソル移動メソッドも、直前の現在行から機能します。

5.1.16 位置決めされた更新

JDBC 2.0 API が Java プログラミング言語で「プログラム的な更新」機能を実現する以前は、結果セットとともにフェッチされた行を変更する唯一の方法は、「位置決めされた更新」と呼ばれる機能を使用することでした。 位置決めされた更新は SQL コマンドを使って実行され、更新対象の結果セットの行を示す名前付きカーソルを必要とします。

Statement インタフェースが提供する setCursorName メソッドを使うと、別の文で生成される次の結果セットと結び付けられるカーソルのカーソル名を、アプリケーションが指定できます。 そして、この名前は、位置決めされた更新または位置決めされた削除の SQL 文の中で、前の文によって生成された ResultSet オブジェクト内の現在行を識別するのに使われます。 位置決めされた更新または削除の機能を結果セットで有効にするためには、これを生成するクエリーは次の形式でなければなりません。

SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . .

FOR UPDATE という語を含めることにより、カーソルが更新をサポートする適切な遮断レベルを持つことが確実になります。

executeQuery メソッドが文に対して呼び出されたあと、得られた ResultSet オブジェクトのカーソル名は ResultSetgetCursorName メソッドを呼び出すことにより取得できます。 DBMS において位置決めされた更新または削除が可能な場合には、更新または削除用の SQL コマンドに対してそのカーソル名をパラメータとして与えることができます。 位置決めされた更新では、 ResultSet オブジェクトを生成した Statement オブジェクトとは異る Statement オブジェクトを使用しなければいけません。 次のコードでは、カーソルを命名し SQL の更新文で使用する方法を示します。 ここで stmt および stmt2 は異なる Statement オブジェクトです。

stmt.setCursorName("x");
ResultSet rs = stmt.executeQuery(
		"SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . .")
String cursorName = rs.getCursorName;
int updateCount = stmt2.executeUpdate(
		"UPDATE . . . WHERE CURRENT OF " + cursorName);

ResultSet オブジェクトに対して getCursorName メソッドが呼び出されたことは、必ずしも JDBC 2.0 コア API で利用可能な ResultSet.updateXXX メソッドを使ってこのオブジェクトが更新可能であることを意味しないことに留意してください。 ResultSet オブジェクトを updateXXX メソッドを使って更新するには、結果セットを生成する executeQuery 文に CONCUR_UPDATABLE 仕様を含める必要があります。 ただし、次のすべての手順を適切に実行するのであれば、この仕様なしで作成された結果セットに対する位置決めされた更新が可能です。 (1) カーソルを命名し、(2) 結果セットを生成する SQL クエリーは、SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . . の形式で指定し、(3) SQL 更新文は UPDATE . . . WHERE CURRENT OF <cursorName> の形式で指定します。

すべての DBMS が位置決めされた更新をサポートしているわけではありません。 DBMS が位置決めされた更新をサポートすることを確かめるためには、アプリケーションは DatabaseMetaData に対して supportsPositionedDelete メソッドおよび supportsPositionedUpdate メソッドを呼び出して、特定の接続がこれらの操作をサポートするかどうかを確認できます。 これらの操作がサポートされている場合、位置決めされた更新が、更新中におかしくなったりその他の競合に終着してしまわないようにするために、DBMS ドライバは、選択されている行が適切に確実にロックされるようにする必要があります。

5.1.17 更新可能な結果セットを生成するクエリー

クエリーの中には、結果セットのタイプにかかわらず、更新不可能な結果セットを生成するものがあります。 たとえば、主キー列を選択していないクエリーに対して、更新不可能な結果セットを生成する場合があります。 データベースの実装上の相違のために、JDBC 2.0 コア API は、更新可能な結果セットを確実に生成する厳密な SQL クエリーのセットを指定していません。 その代わり、更新機能をサポートする JDBC 準拠ドライバのために、更新可能な結果セットを通常生成する仕様のセットを定義しています。 クエリーが次の指針に従っている場合、通常、開発者は更新可能な結果セットが生成されることを期待できます。

  1. そのクエリーが、データベース中のテーブルを 1 つしか参照していない
  2. そのクエリーが、結合操作または GROUP BY 句を含んでいない
  3. そのクエリーが、参照するテーブルの主キーを選択している

    結果セットに対して挿入を実行する場合には、SQL クエリーは上記の 3 つの指針の 1 つに加え、次の 3 つの追加条件を満たす必要があります。

  4. ユーザはテーブルに対して読み取り/書き込みデータベース権限を持っている
  5. そのクエリーが、基となるテーブルに含まれる列のうち、null に設定できない列をすべて選択している
  6. そのクエリーが、デフォルト値を持たない列をすべて選択している

4 番目および 5 番目の条件が必要なのは、列が null 値またはデフォルト値を受け入れるのでない限り、テーブルに挿入される行はそれぞれの列に値を持つ必要があるためです。 挿入操作の実行対象の結果セットに、値を必要としている列がすべて含まれていない場合、挿入は失敗します。

クエリーの実行以外の方法で作成された結果セット (DatabaseMetaData インタフェースのいくつかのメソッドにより返される結果セットなど) は、スクロール可能でも更新可能でもありませんし、その必要もありません。

5.1.18 きわめて大きなサイズの行の値に対するストリームの使用

JDBC 2.0 コア API の新しいインタフェースである Blob および Clob は、SQL3 データ型の BLOB (Binary Large Object) および CLOB (Character Large Object) を Java プログラミング言語にマッピングしたものです。 これらのデータ型を使用することにより、データベースは非常に大きなバイナリまたはキャラクタオブジェクトを確実に格納できるようになります。 この場合、ResultSetgetBlob メソッドおよび getClob メソッドを使用して、格納されたオブジェクトを取得します。

ResultSet オブジェクトは、JDBC 1.0 API だけを使用しても、任意の大きな LONGVARBINARY または LONGVARCHAR データを取得できます。 メソッド getBytesgetString は、1 つの大きなかたまりとしてデータを返します (Statement.getMaxFieldSize の戻り値によって課されている限界まで)。 このデータの大きなかたまりを、小さな固定サイズのかたまりとして取得することも可能です。 これを行うには、データをかたまりで読み取ることができる ResultSet クラスの戻り値 java.io.Input ストリームが必要です。 このストリームは、ResultSet オブジェクトに対する次の getXXX メソッドの呼び出しで自動的に閉じられてしまうため、すぐにアクセスする必要があることに注意してください。この動作は、JDBC API の制限ではなく、あるデータベースシステムの基となる実装によりラージ blob アクセスに適用されてしまう制限です。

JDBC 1.0 API にはストリームを取得する個別メソッドが 3 つあり、それぞれが異なった戻り値を返します。

ASCII 文字と Unicode 文字の両方のストリームを取得する次のメソッドは、JDBC 2.0 コア API で新たに追加されました。

getAsciiStream により返されるストリームは、各バイトが ASCII 文字のバイトストリームを返します。 これは、2 バイトの Unicode 文字のストリームを返す getCharacterStream とは異なります。 getCharacterStream メソッドは、Reader オブジェクトを返す前にドライバが ASCII 文字を Unicode 文字に変換するため、ASCII 文字と Unicode 文字の両方を使用できます。 DBMS およびドライバが JDBC 2.0 API をサポートしていないために getUnicodeStream を使用する必要がある場合、JDBC Unicode ストリームがビッグエンディアンデータを返すことにも留意してください。つまり、これらのストリームはデータを受け取る際最初に高位のバイトを、次に低位のバイトを期待します。 これは、Java プログラミング言語により定義された標準のエンディアンと一致します。これはプログラムの移植性を保つために重要な点です。 ビッグエンディアンの順序に関する詳細は、Tim Lindholm および Frank Yellin 著の JavaTM Virtual Machine 仕様を参照してください。

次のコードは、getAsciiStream メソッドの使用法を示します。

java.sql.Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT x FROM Table2");
// Now retrieve the column 1 results in 4 K chunks:
byte [] buff = new byte[4096];
while (rs.next()) {
	java.io.InputStream fin = rs.getAsciiStream(1);
	for (;;) {
		int size = fin.read(buff);
		if (size == -1) { // at end of stream
					break;
		}


		// Send the newly-filled buffer to some ASCII output stream
		output.write(buff, 0, size);
	}
}

5.1.19 NULL の結果値

指定された結果値が JDBC NULL であるかどうかを判断するため、まず列を読み取ってから、ResultSet.wasNull メソッドを使用する必要があります。これは、ResultSet.getXXX メソッドの 1 つにより取得された JDBC NULL は、値の型により null0、または false のどれかに変換されるためです。

次のリストは、JDBC NULL の取得時に、getXXX メソッドにより返される値を示します。

たとえば、getInt メソッドが null 値を許可する列から 0 を返した場合、次のコードに示すように、アプリケーションはデータベース内の値が 0 または NULL なのか、wasNull メソッドを呼び出すまでは知ることができません。ここで、rs は ResultSet オブジェクトです。

int n = rs.getInt(3);
boolean b = rs.wasNull();

b が true の場合、rs の現在行の第 3 列に格納された値は JDBC NULL です。 wasNull メソッドは、取得した最後の値だけを検査します。このため、n が NULL かどうかを判別するには、別の getXXX メソッドを呼び出す前に wasNull を呼び出す必要があります。

5.1.20 ResultSet オブジェクトのクローズ

通常、ResultSet を閉じるには、何もする必要がありません。 これは、Statement オブジェクトが閉じられるとき、再実行されるとき、または一連の複数結果から次の結果を検索するために使用されるときに、ResultSet を生成した Statement オブジェクトが、ResultSet を自動的に閉じるからです。 close メソッドは、ResultSet オブジェクトを明示的に閉じるために提供されています。このメソッドは、ResultSet オブジェクトが保持していたリソースをすぐに解放します。 これは、複数の文が使われている状況で、データベースのリソースの競合を防ぐのに十分早い時期で自動クローズが行われない場合に、必要となるかもしれません。

5.1.21 JDBC の仕様への準拠

JDBC に準拠したドライバは、通常スクロール可能な結果セットをサポートします。ただし、これは要求されていません。 その目的は、JDBC ドライバが、基となるデータベースシステムが提供しているサポートを使用して、スクロール可能な結果セットを実装することです。 DBMS がスクロール機能をサポートしない場合、ドライバはこの機能を省略することができます。

スクロール機能がオプションであるからといって、この機能の省略が推奨されているわけではありません。 これがオプションになっているのは、スクロール機能をサポートしないデータソースで、JDBC ドライバを実装する際の複雑さを軽減するためだけの目的です。 実際のところ、推奨されている代替手段は、ドライバがスクロール機能を DBMS の最上位にレイヤとして実装することです。 これを実現する 1 つの方法は、結果セットを行セットとして実装することです。 これを実現するメソッドを提供する RowSet インタフェースは、JDBC Standard Extention API の一部分です。



[目次] [前の項目] [次の項目]
Copyright © 1999, Sun Microsystems, Inc. All rights reserved.