JDBC ResultSetインターフェイスのガイド

1. 概要

link:/java-jdbc[Java Database Connectivity(JDBC)API]は、Javaアプリケーションからデータベースへのアクセスを提供します。 サポートされているJDBCドライバーが使用可能であれば、JDBCを使用して任意のデータベースに接続できます。
  • ResultSet_は、データベースクエリの実行によって生成されたデータのテーブルです。*このチュートリアルでは、https://docs.oracle.com/javase/8/docs/api/java/をさらに詳しく見ていきます。 sql / ResultSet.html [_ResultSet API]。

2. _ResultSet_の生成

最初に、_Statement_インターフェイスを実装するオブジェクトで_executeQuery()_を呼び出して、_ResultSet_を取得します。 _PreparedStatement_と_CallableStatement_は両方とも、_Statement_のサブインターフェイスです。
PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();
_ResultSet_オブジェクトは、結果セットの現在の行を指すカーソルを保持します。 _ResultSet_で_next()_を使用して、レコードを反復処理します。
次に、結果を反復処理しながら_getX()_メソッドを使用して、データベース列から値を取得します*。ここで、_X_は列のデータ型です。 実際、データベースの列名を_getX()_メソッドに提供します。
while(rs.next()) {
    String name = rs.getString("name");
    Integer empId = rs.getInt("emp_id");
    Double salary = rs.getDouble("salary");
    String position = rs.getString("position");
}
同様に、*列名の代わりに、_getX()_メソッド*で列のインデックス番号を使用できます。 インデックス番号は、SQL selectステートメントの列のシーケンスです。
selectステートメントが列名をリストしない場合、インデックス番号はテーブル内の列のシーケンスです。 列インデックスの番号付けは1から始まります。
Integer empId = rs.getInt(1);
String name = rs.getString(2);
String position = rs.getString(3);
Double salary = rs.getDouble(4);

3. _ResultSet_からのメタデータの取得

このセクションでは、_ResultSet_の列のプロパティとタイプに関する情報を取得する方法について説明します。
まず、_ResultSet_で_getMetaData()_メソッドを使用して、https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html [_ResultSetMetaData_]を取得します。
ResultSetMetaData metaData = rs.getMetaData();
次に、_ResultSet_にある列の数を取得します。
Integer columnCount = metaData.getColumnCount();
さらに、メタデータオブジェクトで以下のメソッドを使用して、各列のプロパティを取得できます。
  • getColumnName(int columnNumber) _–_列の名前を取得します

  • getColumnLabel(int columnNumber) _–_のラベルにアクセスする
    SQLクエリの_AS_の後に指定されている列

  • getTableName(int columnNumber) _–_この列のテーブル名を取得する
    属する

  • getColumnClassName(int columnNumber) Javaデータを取得する
    列のタイプ

  • getColumnTypeName(int columnNumber) _–_のデータ型を取得する
    データベースの列

  • getColumnType(int columnNumber) _–_のSQLデータ型を取得する
    カラム

  • isAutoIncrement(int columnNumber) _–_は、列が
    自動インクリメントです

  • isCaseSensitive(int columnNumber) _–_は、列が
    ケースの問題

  • isSearchable(int columnNumber) _–_は、列を使用できるかどうかを提案します
    SQLクエリの_where_句で

  • isCurrency(int columnNumber) column列に
    現金価値

  • isNullable(int columnNumber) –_は、列が
    null、列にnull値を含めることができる場合は_one
    、列のnull可能性が不明な場合は_two_

  • isSigned(int columnNumber) _–_は、値が
    列は署名され、そうでない場合は_false_を返します

    列を繰り返して、プロパティを取得しましょう。
for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
    String catalogName = metaData.getCatalogName(columnNumber);
    String className = metaData.getColumnClassName(columnNumber);
    String label = metaData.getColumnLabel(columnNumber);
    String name = metaData.getColumnName(columnNumber);
    String typeName = metaData.getColumnTypeName(columnNumber);
    int type = metaData.getColumnType(columnNumber);
    String tableName = metaData.getTableName(columnNumber);
    String schemaName = metaData.getSchemaName(columnNumber);
    boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber);
    boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber);
    boolean isCurrency = metaData.isCurrency(columnNumber);
    boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber);
    boolean isReadOnly = metaData.isReadOnly(columnNumber);
    boolean isSearchable = metaData.isSearchable(columnNumber);
    boolean isReadable = metaData.isReadOnly(columnNumber);
    boolean isSigned = metaData.isSigned(columnNumber);
    boolean isWritable = metaData.isWritable(columnNumber);
    int nullable = metaData.isNullable(columnNumber);
}
_ResultSet_を取得すると、カーソルの位置は最初の行の前になります。 さらに、デフォルトでは、_ResultSet_は順方向にのみ移動します。 しかし、スクロール可能な_ResultSet_を他のナビゲーションオプションに使用できます。
このセクションでは、さまざまなナビゲーションオプションについて説明します。

4.1. ResultSet Types

_ResultSet_ typeは、データセットをどのように操作するかを示します。
  • _TYPE_FORWARD_ONLY –_デフォルトオプション。カーソルが移動します
    最初から最後まで

  • _TYPE_SCROLL_INSENSITIVE –_カーソルはデータセット内を移動できます
    順方向と逆方向の両方。データセット内を移動中に基礎データに変更がある場合、それらは無視されます。データセットには、データベースクエリが結果を返した時点からのデータが含まれます

  • _TYPE_SCROLL_SENSITIVE –_スクロール不感タイプに似ています。
    ただし、このタイプでは、データセットは基になるデータへの変更をすぐに反映します

    すべてのデータベースがすべての_ResultSet_タイプをサポートしているわけではありません。 _DatabaseMetaData_オブジェクトで_supportsResultSetType_を使用して、タイプがサポートされているかどうかを確認しましょう。
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);

4.2. スクロール可能なResultSet

scrollable _ResultSet_を取得するには、_Statement_ *の準備中にいくつかの追加パラメーターを渡す必要があります。
たとえば、_TYPE_SCROLL_INSENSITIVE_または_TYPE_SCROLL_SENSITIVE_を_ResultSet_型として使用して、スクロール可能な_ResultSet_を取得します。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_INSENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

4.3. ナビゲーションオプション

スクロール可能な_ResultSet_で以下のオプションのいずれかを使用できます。
  • next() – –現在の位置から次の行に進みます

  • previous() –前の行に移動します

  • first()– _SetSet_の最初の行に移動します

  • _last()–_最後の行にジャンプ

  • _beforeFirst()–_は先頭に移動します。 _next()_を呼び出す
    _ResultSet_は、このメソッドを呼び出した後、_ResultSet_から最初の行を返します。

  • _afterLast()–_は最後までジャンプします。 previous()を呼び出す
    ResultSet
    このメソッドを実行すると、_ResultSet_から最後の行が返されます

  • _relative(int numOfRows)–_現在から前方または後方に移動
    _numOfRows_による位置

  • _absolute(int rowNumber)–_指定された_rowNumber_にジャンプ

    いくつか例を見てみましょう。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the results from first to last
}
rs.beforeFirst(); // jumps back to the starting point, before the first row
rs.afterLast(); // jumps to the end of resultset

rs.first(); // navigates to the first row
rs.last(); // goes to the last row

rs.absolute(2); //jumps to 2nd row

rs.relative(-1); // jumps to the previous row
rs.relative(2); // jumps forward two rows

while (rs.previous()) {
    // iterates from current row to the first row in backward direction
}

4.4. _ResultSet_行数

_getRow()_を使用して、_ResultSet_の現在の行番号を取得します。
最初に、_ResultSet_の最後の行に移動し、_getRow()_を使用してレコード数を取得します。
rs.last();
int rowCount = rs.getRow();

5. _ResultSet_のデータの更新

デフォルトでは、_ResultSet_は読み取り専用です。 ただし、更新可能な_ResultSet_を使用して、行を挿入、更新、および削除できます。

5.1. ResultSet Concurrency

同時実行モードは、_ResultSet_がデータを更新できるかどうかを示します。
_CONCUR_READ_ONLY_オプションはデフォルトであり、_ResultSet_を使用してデータを更新する必要がない場合に使用する必要があります。
ただし、_ResultSet_のデータを更新する必要がある場合は、_CONCUR_UPDATABLE_オプションを使用する必要があります。
*すべてのデータベースがすべての_ResultSet_タイプのすべての同時実行モードをサポートしているわけではありません*。 したがって、_supportsResultSetConcurrency()_メソッドを使用して、目的のタイプと同時実行モードがサポートされているかどうかを確認する必要があります。
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetConcurrency(
  ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

5.2. 更新可能な_ResultSet_の取得

更新可能な_ResultSet_を取得するには、_Statement_を準備するときに追加のパラメーターを渡す必要があります。 そのために、ステートメントを作成するときに3番目のパラメーターとして_CONCUR_UPDATABLE_を使用しましょう。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

5.3. 行の更新

このセクションでは、前のセクションで作成した更新可能な_ResultSet_を使用して行を更新します。
_updateX()_メソッドを呼び出して、更新する列名と値を渡すことで、行のデータを更新できます。 _updateX()_メソッドの_X_の代わりに、サポートされている任意のデータ型を使用できます。
タイプ_double_の_“ salary” _列を更新しましょう。
rs.updateDouble("salary", 1100.0);
これは_ResultSet_のデータを更新するだけですが、変更はまだデータベースに保存されていないことに注意してください。
最後に、_updateRow()_を呼び出して、更新をデータベースに保存します*:
rs.updateRow();
列名の代わりに、列のインデックスを_updateX()_メソッドに渡すことができます。 これは、_getX()_メソッドを使用して値を取得するために列インデックスを使用することに似ています。 列名またはインデックスを_updateX()_メソッドに渡すと、同じ結果が得られます。
rs.updateDouble(4, 1100.0);
rs.updateRow();

5.4. 行の挿入

次に、更新可能な_ResultSet_を使用して新しい行を挿入しましょう。
最初に、_moveToInsertRow()_を使用してカーソルを移動し、新しい行を挿入します。
rs.moveToInsertRow();
次に、_updateX()_メソッドを呼び出して、情報を行に追加する必要があります。 データベーステーブルのすべての列にデータを提供する必要があります。 すべての列にデータを提供しない場合、デフォルトの列値が使用されます。
rs.updateString("name", "Venkat");
rs.updateString("position", "DBA");
rs.updateDouble("salary", 925.0);
次に、_insertRow()_を呼び出して、データベースに新しい行を挿入します。
rs.insertRow();
最後に、_moveToCurrentRow()._を使用してみましょう。これにより、_moveToInsertRow()_メソッドを使用して新しい行の挿入を開始する前のカーソル位置に戻ります。
rs.moveToCurrentRow();

5.5. 行を削除する

このセクションでは、更新可能な_ResultSet_を使用して行を削除します。
最初に、削除する行に移動します。 次に、__deleteRow()__methodを呼び出して、現在の行を削除します。
rs.absolute(2);
rs.deleteRow();

6. 保持力

保持機能は、データベーストランザクションの終了時に_ResultSet_を開くか閉じるかを決定します。

* 6.1。 保持性タイプ*

トランザクションのコミット後に_ResultSet_が不要な場合は、_CLOSE_CURSORS_AT_COMMIT_を使用します。
_HOLD_CURSORS_OVER_COMMIT_を使用して、保持可能な_ResultSet_を作成します。 保持可能な_ResultSet_は、データベーストランザクションがコミットされた後でも閉じられません。
すべてのデータベースがすべての保持機能タイプをサポートしているわけではありません。
したがって、_DatabaseMetaData_オブジェクトで_supportsResultSetHoldability()_を使用して、保持機能タイプがサポートされているかどうかを*確認してみましょう。 次に、_getResultSetHoldability()_を使用して、データベースのデフォルトの保持機能を取得します。
boolean isCloseCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
boolean isOpenCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
boolean defaultHoldability
  = dbmd.getResultSetHoldability();

6.2. 保持可能な_ResultSet_

保持可能な_ResultSet_を作成するには、_Statement_を作成するときに、最後のパラメーターとして_holdability_タイプを指定する必要があります。 このパラメーターは、同時実行モードの後に​​指定されます。
Microsoft SQL Server(MSSQL)を使用している場合、_ResultSet_ではなく、データベース接続に保持機能を設定する必要があることに注意してください。
dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
これを実際に見てみましょう。 まず、_Statement_を作成して、保持機能を_HOLD_CURSORS_OVER_COMMIT_に設定します。
Statement pstmt = dbConnection.createStatement(
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE,
  ResultSet.HOLD_CURSORS_OVER_COMMIT)
次に、データを取得しながら行を更新しましょう。 これは、更新トランザクションをデータベースにコミットした後、_ResultSet_を繰り返し処理することを除いて、前に説明した更新の例に似ています。 これは、MySQLデータベースとMSSQLデータベースの両方で正常に機能します。
dbConnection.setAutoCommit(false);
ResultSet rs = pstmt.executeQuery("select * from employees");
while (rs.next()) {
    if(rs.getString("name").equalsIgnoreCase("john")) {
        rs.updateString("name", "John Doe");
        rs.updateRow();
        dbConnection.commit();
    }
}
rs.last();
MySQLは_HOLD_CURSORS_OVER_COMMIT_のみをサポートしていることに注意してください。 そのため、_CLOSE_CURSORS_AT_COMMIT_を使用しても、無視されます。
MSSQLデータベースは、_CLOSE_CURSORS_AT_COMMIT_をサポートしています。 これは、トランザクションをコミットすると、_ResultSet_が閉じられることを意味します。 その結果、トランザクションをコミットした後に_ResultSet_にアクセスしようとすると、「カーソルが開いていません」というエラーが発生します。 したがって、_ResultSet_からさらにレコードを取得することはできません。

7. フェッチサイズ

通常、データを_ResultSet_にロードするとき、データベースドライバーはデータベースからフェッチする行数を決定します。 たとえば、MySQLデータベースでは、_ResultSet_は通常、すべてのレコードを一度にメモリにロードします。
ただし、JVMメモリに収まらない大量のレコードを処理する必要がある場合があります。 この場合、_Statement_または_ResultSet_オブジェクトのfetch sizeプロパティを使用して、最初に返されるレコードの数を制限できます。
追加の結果が必要になるたびに、_ResultSet_はデータベースからレコードの別のバッチをフェッチします。 フェッチサイズプロパティを使用して、*データベーストリップごとにフェッチする行数に関する提案をデータベースドライバーに提供できます*。 指定したフェッチサイズは、後続のデータベーストリップに適用されます。
_ResultSet_のフェッチサイズを指定しない場合、_Statement_のフェッチサイズが使用されます。 _Statement_または_ResultSet_のフェッチサイズを指定しない場合、データベースのデフォルトが使用されます。

7.1. _Statement_でフェッチサイズを使用する

では、_Statement_の実際のフェッチサイズを見てみましょう。 _Statement_のフェッチサイズを10レコードに設定します。 クエリが100レコードを返す場合、データベースの往復が10回あり、そのたびに10レコードがロードされます。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the resultset
}

7.2. _ResultSet_でフェッチサイズを使用する

では、_ResultSet_を使用して、前の例でフェッチサイズを変更しましょう。
最初に、_Statement_でフェッチサイズを使用します。 これにより、クエリの実行後、_ResultSet_が最初に10レコードをロードできます。
次に、_ResultSet_のフェッチサイズを変更します。 これは、以前に_Statement_で指定したフェッチサイズをオーバーライドします。 したがって、すべてのレコードがロードされるまで、後続のすべてのトリップは20レコードをロードします。
その結果、すべてのレコードをロードするためのデータベーストリップは6回のみになります。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

rs.setFetchSize(20);

while (rs.next()) {
    // iterate through the resultset
}
最後に、結果を繰り返しながら_ResultSet_のフェッチサイズを変更する方法を見ていきます。
前の例と同様に、最初に_Statement_でフェッチサイズを10に設定します。 したがって、最初の3回のデータベース旅行では、旅行ごとに10レコードがロードされます。
次に、30番目のレコードを読み取りながら、_ResultSet_のフェッチサイズを20に変更します。 したがって、次の4回の旅行では、各旅行につき20レコードがロードされます。
したがって、100件すべてのレコードをロードするには、7回のデータベーストリップが必要です。
PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

int rowCount = 0;

while (rs.next()) {
    // iterate through the resultset
    if (rowCount == 30) {
        rs.setFetchSize(20);
    }
    rowCount++;
}

8. 結論

この記事では、_ResultSet_ APIを使用してデータベースからデータを取得および更新する方法を説明しました。 ここで説明したいくつかの高度な機能は、使用しているデータベースに依存しています。 したがって、使用する前にこれらの機能のサポートを確認する必要があります。
いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/core-java-persistence[GitHubで]から入手できます。