目次 | 前の項目 | 次の項目 | JDBCTM ガイド: 使用の開始 |
スクロール非反映型の結果セットでは、通常、開かれている間に加えられた変更は反映されません。スクロール非反映型の結果セットは、基盤となるデータの静的なビューを提供するだけです。スクロール非反映型の結果セットに含まれる行、その順序、および列の値は、結果セットの作成時に固定されます。
一方、スクロール反映型の結果セットでは、開かれている間に加えられた変更が反映されます。 つまり、基盤となるデータの「動的」なビューを提供します。したがって、基盤となる列の値が変更されると、スクロール反映型結果セットには、その変更内容が反映されます。スクロール反映型結果セットに含まれる行とその順序は固定することも可能です (実装で定義)。
読み取り専用の並行処理を使用する結果セットでは、その内容を更新することはできません。読み取り専用ロックは、同じデータ項目に対して同時にいくつでも保持できるので、読み取り専用の並行処理を使用すれば、トランザクション間で並行処理の全体レベルを上げることができます。
更新可能な結果セットは更新が可能で、このような結果セットではデータベース書き込みロックを使用して、同じデータ項目に対する複数のトランザクションからのアクセスを調整することができます。書き込みロックは 1 つのデータ項目に対して 1 つしか保持できないため、並行処理数を減らすことができます。データアクセスの衝突がまれにしか起こらないと考えられる場合は、オプティミスティック並行処理制御方式が使用できます。一般に、オプティミスティック並行処理制御では、行を値またはバージョン番号で比較することで、更新の衝突が発生したかどうかを判断します。
Connection con = DriverManager.getConnection( "jdbc:my_subprotocol:my_subname"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT emp_no, salary FROM employees");
次の例では、更新可能で、かつ更新内容が反映されるスクロール可能な結果セットを作成しています。行の要求時には、一度に 25 行がデータベースから取り出されます。
次の例では、上の例と同じ属性を指定して結果セットを作成していますが、結果セットの生成に PreparedStatement を使用しています。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");
JDBC ドライバがどのようなタイプの結果セットをサポートしているかは、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();
DatabaseMetaData.supportsResultSetType()
メソッドで確認できます。しかし、JDBC ドライバがサポートしていないタイプの結果セットが使用されて、Statement
オブジェクト、PreparedStatement
オブジェクト、CallableStatement
オブジェクトの作成が要求される可能性もあります。ドライバ側は、このような場合は、その文を生成する Connection
で SQLWarning
を発行するとともに、以下の規則に従って、別の結果セットタイプ値を選ぶようにすべきです。
DatabaseMetaData.supportsResultSetConcurrency()
メソッドで確認できます。サポートしていないタイプの並行処理が要求された場合は、その文を生成する Connection
で SQLWarning
を発行するとともに、別の並行処理を選ぶようにすべきです。サポートしていないタイプの結果セットとサポートしていないタイプの並行処理が同時に要求された場合は、結果セットのタイプを先に選ぶようにします。
ある場合には、JDBC ドライバ側で、ResultSet
に対して文の実行時に別のタイプの結果セットまたは並行処理を選ばなければならないことがあります。たとえば、複数のテーブルにまたがった結合が行われる SELECT 文では、更新可能な ResultSet
は生成できません。このような場合には、ドライバ側は、その ResultSet
を生成する Statement
、PreparedStatement
、または CallableStatement
で SQLWarning
を発行するとともに、前述したとおり、適切なタイプの結果セットまた並行処理を選ぶようにすべきです。アプリケーション側では、ResultSet.getType()
メソッドと getConcurrency()
メソッドを呼び出すことで、それぞれ結果セットと並行処理の実際のタイプを知ることができます。
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 では規定していません。しかし、一般的には、次の条件を満たしたクエリーであれば、更新可能な結果セットを得ることができます。
また、挿入操作を行う場合は、次の条件も満たしている必要があります。
結果セットに対して順方向の繰り返し処理を行うには、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
インタフェースの説明を見てくると、スクロール可能な結果セットの行に対して繰り返し処理を行う方法はほかにもあると思えます。しかし、次の例をよく見てみてください。 この方法は正しくありません。
この例では、スクロール可能な結果セットに対して順方向の繰り返し処理を行おうとしていますが、いくつかの理由でこの方法は正しくありません。問題の 1 つは、結果セットが空の場合に// incorrect!!! while (!rs.isAfterLast()) { rs.relative(1); System.out.println(rs.getString("emp_no") + " " + rs.getFloat("salary")); }
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); } }
JDBC API で提供されている結果セットのタイプ (順方向専用、スクロール非反映型、およびスクロール反映型) によって、基盤となるデータの変更がアプリケーションから見えるかどうかという点で大きな違いがあります。特にスクロールをサポートしているタイプの結果セットでは、開かれている間、特定の行に何回も移動できるので、結果セットに変更内容が反映されるかどうかという点は興味深い問題です。
con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);
con
は、Connection
型の変数です。システム内のすべてのトランザクションが TRANSACTION_READ_COMMITTED かそれ以上の遮断レベルで実行される場合、トランザクションからは、ほかのトランザクションがコミットした変更だけが見えます。また、結果セットが開かれているときに、その結果セットが属するトランザクションから見える変更は、その結果セット全体から常に見えます。これは、あるトランザクションによって行われた更新が別のトランザクションから見えることを表します。
しかし、結果セットが開かれている間に加えられた変更についてはどうでしょうか。そのような変更は、たとえば ResultSet.getXXX()
を使用することによって結果セット全体から見えるのでしょうか。結果セットが開かれている間、結果セットには、ほかのトランザクションによって基盤となるデータに加えられた変更が反映されるか、同じトランザクションに属するほかの結果セットによって加えられた変更が反映されるか、自分自身が行なった変更が反映されるかは、結果セットのタイプによります。 なお、ほかのトランザクションと、同じトランザクションに属するほかの結果セットによって加えられた変更を、まとめて「他による変更」と呼ぶことにします。
スクロール反映型結果セットは、これとは反対の性質を持ちます。スクロール反映型結果セットには、それが属するトランザクションから見える他によって加えられた更新がすべて反映されます。ただし、挿入と削除については反映されません。
更新が反映されるとはどういうことか、注意深く見ていきましょう。ほかのトランザクションによって行われた更新により特定の行があるべき場所が変更された場合、つまり、行が削除され、次に挿入された場合、その行は、結果セットが再度開かれるまで移動しません。また、更新により、ある行が結果セットの構成行でなくなった場合、つまり、行が削除された場合、その行は、結果セットが再度開かれるまで結果セット上から消えません。スクロール反映型結果セットでは、ある行がほかのトランザクションによって明示的に削除された場合、絶対的位置指定により行の論理的な取り出しが行えるように、その行の部分にプレースホルダが挿入されます。ただし、更新された列の値は常に反映されます。
DatabaseMetaData
インタフェースを使用すると、結果セットがサポートしている機能を具体的に調べることができます。 たとえば、 othersUpdatesAreVisible
、othersDeletesAreVisible
、othersInsertsAreVisible
の新しい 3 つのメソッドがこの目的に使用できます。
順方向専用の結果セットは、実際には、スクロール非反映型かスクロール反映型のどちらかの結果セットが退歩した形です。 これは、結果セットを生成するクエリーを DBMS がどう評価したかによります。クエリーの種類によりますが、ほとんどの DBMS はクエリーの結果をインクリメンタルに生成する機能を持っています。クエリーの結果がインクリメンタルに生成される場合、データ値は DBMS が要求するまで実際には取得されず、また、結果セットは反映型の結果セットのように動作します。しかし、インクリメンタルな生成ができないクエリーもあります。たとえば、結果セットがソートされている場合は、DBMS により結果セットの先頭行がアプリケーションに返される前に、事前に結果セット全体を生成しておかなければならないことがあります。このような場合は、順方向専用の結果セットはスクロール非反映型の結果セットのように動作します。
TYPE_FORWARD_ONLY
の結果セットでは、othersUpdatesAreVisible
、othersDeletesAreVisible
、othersInsertsAreVisible
の各メソッドを使用すれば、DBMS によって結果セットがインクリメンタルに生成されたときに挿入、更新、および削除が反映されるかどうかを調べることができます。クエリーの結果がソートされている場合は、これらのメソッドが true を返した場合であってもインクリメンタルな生成ができない場合があり、また、変更は反映されません。
DatabaseMetaData
のメソッド (ownUpdatesAreVisible
、ownDeletesAreVisible
、および ownInsertsAreVisible
) で調べることができます。変更が反映されるかどうかは DBMS ごと、JDBC ドライバごとに異なるため、これらのメソッドが用意されています。
updateXXX()
の呼び出しのあとに getXXX()
を呼び出して、更新された列の値を取得できる場合は、結果セット自身による更新が反映されていることになります。updateXXX()
を呼び出したあとも getXXX()
で古い列の値が返される場合は、更新は反映されていないことになります。同様に、insertRow()
を呼び出したあと、挿入した行が結果セットに現れる場合は、挿入が反映されていることになります。insertRow()
を呼び出したあと、結果セットを開き直さない状態で、挿入した行が結果セットに現れない場合は、挿入は反映されていないことになります。行の削除後、結果セットからその行が消えている場合、または、結果セット内に空きができている場合は、削除が反映されていることになります。
次の例では、TYPE_SCROLL_SENSITIVE
の結果セットで、その結果セット自身による変更が反映されるかどうかを調べる方法を示しています。
DatabaseMetaData dmd; ... if (dmd.ownUpdatesAreVisible
(ResultSet.TYPE_SCROLL_INSENSITIVE)) { // changes are visible }
ResultSet.wasUpdated()
、wasDeleted()
、wasInserted()
の各メソッドを使用して調べることができます。結果セットが変更を検出できるかどうかと、変更が反映されるかどうかは別の問題です。つまり、変更が反映されても、自動的には検出されないということです。
DatabaseMetaData
インタフェースには、特定のタイプの結果セットについて JDBC ドライバがその変更を検出できるかどうかを調べることができるメソッドがあります。次に例を示します。
boolean bool = dmd.deletesAreDetected( ResultSet.TYPE_SCROLL_SENSITIVE);
deletesAreDetected が true
を返した場合は、ResultSet.wasDeleted()
を使用することで、TYPE_SCROLL_SENSITIVE
の結果セット内に空きができているかどうかを検出できます。
ResultSet.setFetchSize()
を参照)、更新が反映される結果セットを使用している場合であっても、アプリケーションは行に対して加えられた最新の変更を取得できないことがあります。このような場合に ResultSet.refreshRow()
を使用すると、データベースに格納されている最新の値で行を更新するようドライバに要求できます。JDBC ドライバは、フェッチサイズが 2 以上の場合は一度に複数の行を取り出すことがあります。 refreshRow()
を頻繁に呼び出すとパフォーマンスが低下する可能性があるため、refreshRow()
の呼び出しは必要最小限にしてください。