注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいています。
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
次のコードは、列 a
を int
、列 b
を String
、そして列 c
を float
とする行のコレクションを戻す 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); }
結果セットも行および列を含むテーブルですが、結果セットには、クエリーの条件を満たすデータベーステーブルから取得した列値だけが含まれます。 言い換えると、結果セット行には、基になるデータベーステーブル内の列のサブセットが含まれます (ただし、クエリーによりテーブル内のすべてが選択される場合を除きます。その場合には、結果セットテーブルには、データベーステーブル内のすべての行のすべての列値が含まれます)。過去において、リレーショナルデータベーステーブル (つまり結果セットテーブル) の列値は、不可分 (atomic) な値、つまり 1 つの分割不可能な値でなければなりませんでした。 たとえば、配列は複数の要素で構成されるため、配列を列値とすることはできませんでした。 しかし、SQL3 データ型の出現によって、テーブル列に含めることのできる内容は劇的に拡張されました。 現在では、配列やユーザ定義の構造化型でさえも列値にできます。 この新たな機能により、リレーショナルデータベースは複合型のインスタンスを列値として格納できるようになりました。その結果、リレーショナルデータベースはオブジェクトデータベースにより近いものとなり、リレーショナルデータベースとオブジェクトデータベースとの境界はあいまいになりつつあります。 プログラマは、SQL3 型をサポートした JDBC 2.0 ドライバを使用することにより、これらの新しいデータ型を活用できます。
ResultSet
オブジェクトは、現在行のデータを指し示すカーソルを維持します。 カーソルは next
メソッドが呼び出されるたびに 1 行ずつ下に移動します。 ResultSet
の最初の作成時に、カーソルが最初の行の前に配置されます。このため、next
メソッドの最初の呼び出しにより、カーソルは最初の行に移動して、そこが現在行になります。 ResultSet
の行は、最上行から順次下に取得され、次の next
メソッドの呼び出しのたびにカーソルが 1 行ずつ下に移動します。 カーソルが順方向にだけ移動するというこの能力は、ResultSet
のデフォルト動作で、JDBC 1.0 API だけを実装するドライバはこの動作のみ可能です。 この種の結果セットは、ResultSet.TYPE_FORWARD_ONLY
型を持ち、順方向専用の結果セットと呼ばれます。
ドライバがカーソル移動メソッドを JDBC 2.0 コア API で実装している場合、その結果セットはスクロール可能になります。 スクロール可能な結果セットのカーソルは、特定の行への移動に加え、順方向にも逆方向にも移動可能です。 メソッド previous
、first
、last
、absolute
、relative
、afterLast
、および beforeFirst
は、それぞれ、カーソルを現在行から逆方向に、最初の行、最後の行、特定の行番号等に移動します。 結果セットをスクロール可能にする方法およびその実例については、「タイプの異なる結果セットの作成」で説明します。
カーソルが ResultSet
オブジェクト内のある行 (先頭行の前や最終行の後ではなく) に位置する場合、その行が現在行となります。 つまり、カーソルがその行に位置している間に呼び出されたメソッドは、(1) その行の値に基づいて処理を行うか (getXXX
や updateXXX
などのメソッド)、(2) その行全体に対して処理を行うか (updateRow
、insertRow
、deleteRow
、refresh-Row
などのメソッド)、または (3) その行を他の行に移動するための始点として使用します (relative
などのメソッド)。
カーソルは、ResultSet
オブジェクトまたはその上位の Statement
オブジェクトが閉じられるまで有効になっています。
前のセクションで説明したように、順方向のみの結果セットでは、標準的なカーソル移動は、next
メソッドを使って、結果セットの各行を 1 回のみ最初から最後まで順番に移動するものです。 スクロール可能な結果セットでは、ある行に戻ったり、結果セットに対して何回でも繰り返して処理を行うことが可能です。 これが可能なのは、カーソルを先頭行の前にいつでも移動できるためです (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.
ResultSet
インタフェースの beforeFirst
メソッドは、カーソルを最初の行に配置するため、ResultSet.relative(1)
メソッドは意図したとおりに各行を処理します。
if (rs.first()) { while (!rs.isAfterLast()) { System.out.println( rs.getString("EMP_NO") + " " + rs.getFloat("SALARY")); rs.relative(1); } }
新しいカーソル移動関連のメソッドを使用すると、スクロール可能な 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 }
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
オブジェクトの列の番号、型、およびプロパティが得られます。
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
メソッドが推奨されていることを示します。
結果セットは、さまざまなレベルの機能を持ちます。 たとえば、結果セットはスクロール可能またはスクロール不可能です。 スクロール可能な結果セットは、順方向および逆方向への移動に加え、特定の行への移動も可能なカーソルを持っています。 また、結果セットが開かれている間に行われた変更を反映するかどうかも設定できます。つまり、データベース内で修正された列値への変更を反映するかどうかを決定できます。 開発者は、ResultSet
オブジェクトに機能を追加すると、オーバーヘッドが増加することを常に念頭に置く必要があります。このため、必要な場合だけ使用するようにしてください。
結果セットは、タイプの異なる更新機能を複数持つことができます。 スクロール機能に関しては、ResultSet
オブジェクトを更新可能にするとオーバーヘッドが増えるため、必要な場合にだけ使用する必要があります。 つまり、利便性のより高い方法は、更新をプログラム的に実行することであり、これは結果セットが更新可能な場合にのみ行うことができます。 JDBC 2.0 コア API は、2 つの更新機能を提供します。各機能は、ResultSet
インタフェース内の次の定数で指定されます。
ResultSet
オブジェクトでロックを設定する場合には、読み取り専用ロックが使用されます。 これにより、ユーザはデータを読み取ることができますが、変更はできません。 データに対して同時に設定可能な読み取り専用ロックの数は無制限であるため、DBMS またはドライバが制限を課さない限り、同時使用するユーザ数には制限がありません。
より高度な並行処理を実現するために、更新可能な結果セットがオプティミスティック並行処理制御方式を使用するように実装することもできます。 この実装では、競合がまれにしか起こらないこと、および競合が発生した場合には書き込み専用ロックを使用して回避するという前提のもとに、より多くのユーザがデータへ同時にアクセスすることを許可します。 いかなる更新を実行するよりも前に、行を値またはバージョン番号で比較することにより、競合が発生したかどうかをチェックします。 2 つのトランザクション間で更新時に競合が発生した場合は、どちらかのトランザクションが中止されて一貫性が維持されます。 オプティミスティック並行処理制御実装は、並行性を向上させることができます。ただし、競合が多すぎるとパフォーマンスが実質的に低下します。
多くの DBMS およびドライバは、さまざまな状況で最良のパフォーマンスを提供できるよう最適化されています。このため、一般に、データベースプログラマはデフォルトの設定を使用することが推奨されています。 ただし、特定のアプリケーションに対してデータベースのパフォーマンスをチューニングする場合には、JDBC 2.0 API は結果セットデータへのアクセス効率を向上させるヒントをドライバに与えるメソッドを提供します。 これらのパフォーマンスヒントはあくまでもヒントであるため、JDBC 準拠のドライバはこれらを無視することもできます。
次の 2 つのヒントは、ドライバに対し、パフォーマンスを向上させるための指針を与えます。
Statement.setFetchSize
および ResultSet.setFetchSize
という 2 つの異なるメソッドを使用して設定できます。ResultSet
オブジェクトを生成する文は、ResultSet
オブジェクトのデフォルトフェッチサイズを Statement
の setFetchSize
メソッドを使用して設定します。 次のコードは、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
メソッドは、特定のフェッチサイズが特定のアプリケーションに対してデフォルト値よりも効果的かどうかを簡単に試すための機能を、プログラマに対して提供します。
ResultSet
インタフェースは、行の処理方向を指定する、FETCH_FORWARD
、FETCH_REVERSE
、FETCH_UNKNOWN
という 3 つの定数を定義しています。 フェッチサイズと同様、フェッチ方向を設定する 2 つのメソッドが存在します。1 つは Statement
インタフェースのメソッドであり、もう 1 つは ResultSet
インタフェースのメソッドです。 結果セットを作成する文は、Statement
の setFetchDirection
メソッドを使用してデフォルトのフェッチ方向を判別します。 次のコードは、下から上に行の処理が行われるように ResultSet
オブジェクト rs のフェッチ方向を設定します。 フェッチ方向が変更されるまで、オブジェクト stmt により作成されるすべての結果セットのフェッチ方向は、自動的に逆方向になります。
結果セットのデフォルトフェッチ方向は、Statement stmt = con.createStatement(); stmt.setFetchDirection(FETCH_REVERSE); ResultSet rs = stmt.executeQuery(SELECT * FROM EMPLOYEES);
ResultSet
の setFetchDirection
メソッドを使用することにより、いつでも変更可能です。 次のコード行は、前のコードからの継続で、rs のフェッチ方向を順方向に変更します。
rs.setFetchDirection(FETCH_FORWARD);
ResultSet
オブジェクト rs は、ドライバが行を順方向に処理するというヒントを提供します。 このヒントは、ResultSet.setFetchDirection
メソッドが rs に対して再度呼び出されて、示唆されたフェッチ方向が変更されるまで有効です。
フェッチサイズと同様、ドライバは一般にもっとも効率的なフェッチ方向を使用するよう最適化されているため、デフォルト値を変更すると最適化された設定に反して動作してしまうことがあります。 setFetchDirection
メソッドは、パフォーマンスを向上させる目的でアプリケーションのチューニングを簡単に試す機能をブログラマに提供します。
結果セットは、クエリーを実行することで作成されます。結果セットのタイプは Connection
の createStatement
メソッド (または prepareStatement
か prepareCall
) に渡される引数によって異なります。 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 の結果セットが作成されます。結果セットはそのフェッチサイズを変更できますが、型と並行性を変更することはできません。
既に説明したとおり、結果セットをスクロール可能または更新可能にすることには代償が伴います。このため、これらの機能を持つ結果セットは、必要な場合にだけ作成することをお勧めします。
PreparedStatement
および CallableStatement
オブジェクトは、Statement
インタフェースで定義されたメソッドを継承するため、いくつかの異なるタイプの ResultSet
オブジェクトを生成できます。
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 に対してはトランザクション遮断レベルは設定されないため、基盤となるデータベースの遮断レベルがデフォルトで設定されます。
JDBC 2.0 API での新機能の追加により、DBMS またはドライバがサポートしない機能をアプリケーションが要求できるようになりました。 たとえば、ドライバがスクロール可能な結果セットをサポートしない場合、順方向のみの結果セットを返すことができます。 また、クエリーによっては更新不可能な結果セットを返すものがあります。この場合、更新可能な結果セットを要求してもこれらのクエリーには何の影響もありません。 一般的な規則として、クエリーが選択する列の 1 つに主キーを含むこと、およびクエリーを 1 つのテーブルだけを参照するようにすることを念頭に置いてください。
JDBC 2.0 API の新規メソッドを使用すると、アプリケーションは、ドライバがサポートする結果セット機能を識別できます。 ある機能がサポートされているかどうかが疑わしい場合、その機能を要求する前に次のメソッドを呼び出すことをお勧めします。 次の DatabaseMetaData
のメソッドは、ドライバが指定された結果セットのタイプまたは指定された結果セットの並行処理をサポートするかどうかを示します。
DatabaseMetaData.supportsResultSetType
- 指定された結果セットタイプをドライバがサポートするかどうかを示す boolean
値を返します。
DatabaseMetaData.supportsResultSetConcurrency
- ドライバが、指定された並行処理のタイプを、指定された結果セットタイプと組み合わせてサポートするかどうかを示す boolean
値を返します。 次の ResultSet
メソッドは、メソッドの呼び出し先である特定の結果セットに対する結果セットタイプおよび結果セットの並行性を返します。
ResultSet.getType
- 結果セットのタイプを返します。
ResultSet.getConcurrency
- 結果セットの並行処理モードを返します。 アプリケーションがスクロール可能な結果セットを指定し、かつドライバがスクロールをサポートしない場合、ドライバは文を作成した Connection
オブジェクトに対して警告を発行して、順方向のみの結果セットを返します。 たとえドライバがスクロール可能な結果セットをサポートするとしても、アプリケーションは、ドライバがサポートしないスクロール可能タイプを要求する可能性があります。 その場合、ドライバは文を作成した Connection
オブジェクトに対して SQLWarning
を発行して、サポートするタイプのスクロール可能結果セットを (要求されたタイプと正確に一致しない場合でも) 返します。 たとえば、アプリケーションが TYPE_SCROLL_SENSITIVE
結果セットを要求したがドライバがそのタイプをサポートしない場合、ドライバは TYPE_SCROLL_INSENSITIVE
結果セットをサポートするならば、それを返します。 ドライバは、未サポートの結果セットタイプを要求する文を生成した Connection
オブジェクトに対して SQLWarning
を発行することにより、アプリケーションに対して、要求されたものと正確に一致するタイプを返さないことを警告します。
同様に、アプリケーションが更新可能な結果セットを指定すると、更新可能な結果セットをサポートしないドライバは、文を生成した Connection
オブジェクトに対して SQLWarning
を発行して、読み取り専用の結果セットを返します。 アプリケーションが未サポートの結果セットタイプおよび未サポートの並行処理タイプを要求する場合、ドライバは結果セットタイプの方を最初に選択します。
ある場合には、文の実行時に、ドライバ側で別のタイプの結果セットまたは並行処理を選ばなければならないことがあります。 たとえば、複数のテーブルにまたがった結合が行われる SELECT
文では、更新不可能な結果セットが生成される場合があります。 その場合、ドライバは、Connection
オブジェクトに対して警告を発行する代わりに結果セットを作成しようとした Statement
、PreparedStatement
、または CallableStatement
オブジェクトに対して SQLWarning
を発行します。 次に、ドライバは、上記の 2 つの段落に示した指針に従って、適切な結果セットタイプと並行処理タイプのいずれかまたは両方を選択します。
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
メソッドを使って取得されます。この列値を int
値 88
で更新するために 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 列に int
値 88
を割り当てることにより更新します。
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
オブジェクト内で実行されます。詳細は、「バッチ更新の送信」を参照してください。
JDBC 2.0 API は、ResultSet
オブジェクト内の行を Java プログラミング言語のメソッドだけを使用して削除することができるように、deleteRow
メソッドを提供します。 このメソッドは現在行を削除します。このため、deleteRow
を呼び出す前に、アプリケーションはカーソルを削除する行に配置する必要があります。 結果セット内のある行にだけ有効な updateXXX
メソッドとは異なり、このメソッドは結果セット内の現在行とデータベース内の基となる行の両方に対して効果があります。 次の 2 つのコード行は、ResultSet
オブジェクト rs の先頭行を削除し、かつその基となる行 (データベーステーブルの先頭行かもしれないし、そうでないかもしれない) をデータベースから削除します。
rs.first(); rs.deleteRow();
JDBC 2.0 コア API で新たに追加されたメソッドを使って、新規の行を結果セットテーブルおよび基となるデータベーステーブルに挿入できます。 これを可能にするために、API は「挿入行」という概念を定義しています。 これは、結果セットの一部ではなく、結果セットに関連付けられた特別な行で、新しい行を作成するための準備領域として使用されるものです。 挿入行にアクセスには、アプリケーションは ResultSet
の moveToInsertRow
メソッドを呼び出してカーソルを挿入行に配置します。 次に、適切な 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
メソッドが呼び出される直前に現在行であった行にカーソルを戻します。 現在行との相対位置を使用するメソッドを含む、他のカーソル移動メソッドも、直前の現在行から機能します。
JDBC 2.0 API が Java プログラミング言語で「プログラム的な更新」機能を実現する以前は、結果セットとともにフェッチされた行を変更する唯一の方法は、「位置決めされた更新」と呼ばれる機能を使用することでした。 位置決めされた更新は SQL コマンドを使って実行され、更新対象の結果セットの行を示す名前付きカーソルを必要とします。
Statement
インタフェースが提供する setCursorName
メソッドを使うと、別の文で生成される次の結果セットと結び付けられるカーソルのカーソル名を、アプリケーションが指定できます。 そして、この名前は、位置決めされた更新または位置決めされた削除の SQL 文の中で、前の文によって生成された ResultSet
オブジェクト内の現在行を識別するのに使われます。 位置決めされた更新または削除の機能を結果セットで有効にするためには、これを生成するクエリーは次の形式でなければなりません。
SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . .
FOR UPDATE
という語を含めることにより、カーソルが更新をサポートする適切な遮断レベルを持つことが確実になります。
executeQuery
メソッドが文に対して呼び出されたあと、得られた ResultSet
オブジェクトのカーソル名は ResultSet
の getCursorName
メソッドを呼び出すことにより取得できます。 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 ドライバは、選択されている行が適切に確実にロックされるようにする必要があります。
クエリーの中には、結果セットのタイプにかかわらず、更新不可能な結果セットを生成するものがあります。 たとえば、主キー列を選択していないクエリーに対して、更新不可能な結果セットを生成する場合があります。 データベースの実装上の相違のために、JDBC 2.0 コア API は、更新可能な結果セットを確実に生成する厳密な SQL クエリーのセットを指定していません。 その代わり、更新機能をサポートする JDBC 準拠ドライバのために、更新可能な結果セットを通常生成する仕様のセットを定義しています。 クエリーが次の指針に従っている場合、通常、開発者は更新可能な結果セットが生成されることを期待できます。
GROUP BY
句を含んでいない
結果セットに対して挿入を実行する場合には、SQL クエリーは上記の 3 つの指針の 1 つに加え、次の 3 つの追加条件を満たす必要があります。
4 番目および 5 番目の条件が必要なのは、列が null 値またはデフォルト値を受け入れるのでない限り、テーブルに挿入される行はそれぞれの列に値を持つ必要があるためです。 挿入操作の実行対象の結果セットに、値を必要としている列がすべて含まれていない場合、挿入は失敗します。
クエリーの実行以外の方法で作成された結果セット (DatabaseMetaData
インタフェースのいくつかのメソッドにより返される結果セットなど) は、スクロール可能でも更新可能でもありませんし、その必要もありません。
JDBC 2.0 コア API の新しいインタフェースである Blob
および Clob
は、SQL3 データ型の BLOB
(Binary Large Object) および CLOB
(Character Large Object) を Java プログラミング言語にマッピングしたものです。 これらのデータ型を使用することにより、データベースは非常に大きなバイナリまたはキャラクタオブジェクトを確実に格納できるようになります。 この場合、ResultSet
の getBlob
メソッドおよび getClob
メソッドを使用して、格納されたオブジェクトを取得します。
ResultSet
オブジェクトは、JDBC 1.0 API だけを使用しても、任意の大きな LONGVARBINARY
または LONGVARCHAR
データを取得できます。 メソッド getBytes
と getString
は、1 つの大きなかたまりとしてデータを返します (Statement.getMaxFieldSize
の戻り値によって課されている限界まで)。 このデータの大きなかたまりを、小さな固定サイズのかたまりとして取得することも可能です。 これを行うには、データをかたまりで読み取ることができる ResultSet
クラスの戻り値 java.io.Input
ストリームが必要です。 このストリームは、ResultSet
オブジェクトに対する次の getXXX
メソッドの呼び出しで自動的に閉じられてしまうため、すぐにアクセスする必要があることに注意してください。この動作は、JDBC API の制限ではなく、あるデータベースシステムの基となる実装によりラージ blob アクセスに適用されてしまう制限です。
JDBC 1.0 API にはストリームを取得する個別メソッドが 3 つあり、それぞれが異なった戻り値を返します。
getBinaryStream
- データベースからの生のバイトデータを生成するストリームを返します。 まったく変換しません。
getAsciiStream
- 1 バイトの ASCII 文字を生成するストリームを返します。 このメソッドは、文字を ASCII 形式で格納している DBMS ではより効率的に機能します。
getUnicodeStream
- 2 バイトの Unicode 文字を生成するストリームを返します。 このメソッドは、依然として利用可能ですが、新規メソッド getCharacterStream
にすでに置き換えられています。 (以下を参照) ASCII 文字と Unicode 文字の両方のストリームを取得する次のメソッドは、JDBC 2.0 コア API で新たに追加されました。
getCharacterStream
- Unicode 文字を生成する java.io.Reader
オブジェクトを返します。 DBMS が文字を格納する方法には関係なく、ドライバは文字を Unicode 文字のストリームとして返します。
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 仮想マシン仕様を参照してください。
次のコードは、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); } }
指定された結果値が JDBC NULL
であるかどうかを判断するため、まず列を読み取ってから、ResultSet.wasNull
メソッドを使用する必要があります。これは、ResultSet.getXXX
メソッドの 1 つにより取得された JDBC NULL
は、値の型により null
、0
、または false
のどれかに変換されるためです。
次のリストは、JDBC NULL
の取得時に、getXXX
メソッドにより返される値を示します。
null
- Java プログラミング言語でオブジェクトを返す getXXX
メソッドの場合 (getString
、getBigDecimal
、getBytes
、getDate
、getTime
、getTime-stamp
、getAsciiStream
、getCharacterStream
、getUnicodeStream
、getBinary-Stream
、getObject
、getArray
、getBlob
、getClob
、および getRef
)
0
(ゼロ) - getByte
、getShort
、getInt
、getLong
、getFloat
、および getDouble
の場合
false
- getBoolean
の場合 たとえば、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
を呼び出す必要があります。
通常、ResultSet
を閉じるには、何もする必要がありません。 これは、Statement
オブジェクトが閉じられるとき、再実行されるとき、または一連の複数結果から次の結果を検索するために使用されるときに、ResultSet
を生成した Statement
オブジェクトが、ResultSet
を自動的に閉じるからです。 close
メソッドは、ResultSet
オブジェクトを明示的に閉じるために提供されています。このメソッドは、ResultSet
オブジェクトが保持していたリソースをすぐに解放します。 これは、複数の文が使われている状況で、データベースのリソースの競合を防ぐのに十分早い時期で自動クローズが行われない場合に、必要となるかもしれません。
スクロール機能がオプションであるからといって、この機能の省略が推奨されているわけではありません。 これがオプションになっているのは、スクロール機能をサポートしないデータソースで、JDBC ドライバを実装する際の複雑さを軽減するためだけの目的です。 実際のところ、推奨されている代替手段は、ドライバがスクロール機能を DBMS の最上位にレイヤとして実装することです。 これを実現する 1 つの方法は、結果セットを行セットとして実装することです。 これを実現するメソッドを提供する RowSet
インタフェースは、JDBC Standard Extention API の一部分です。