Guía de la interfaz ResultSet de JDBC

1. Información general

La API de Java Database Connectivity (JDBC) proporciona acceso a la base de datos desde una aplicación Java. Podemos usar JDBC para conectarnos a cualquier base de datos siempre que el controlador JDBC compatible esté disponible.

El ResultSet es una tabla de datos generada al ejecutar consultas a la base de datos. En este tutorial, vamos a echar un vistazo más profundo a la API ResultSet .

2. Generación de un conjunto de resultados

Primero, recuperamos un ResultSet llamando a executeQuery () en cualquier objeto que implemente la interfaz Statement . Tanto el PreparedStatement como el CallableStatement son subinterfaces de Statement :

PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees"); ResultSet rs = pstmt.executeQuery();

El objeto ResultSet mantiene un cursor que apunta a la fila actual del conjunto de resultados. Usaremos next () en nuestro ResultSet para iterar a través de los registros.

A continuación, usaremos los métodos getX () mientras iteramos a través de los resultados para obtener los valores de las columnas de la base de datos , donde X es el tipo de datos de la columna. De hecho, proporcionaremos los nombres de las columnas de la base de datos a los métodos 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"); } 

Asimismo, el número de índice de la columna se puede utilizar con los métodos getX () en lugar del nombre de la columna. El número de índice es la secuencia de las columnas en la instrucción de selección de SQL.

Si la instrucción de selección no enumera los nombres de las columnas, el número de índice es la secuencia de columnas en la tabla. La numeración del índice de columna comienza desde uno:

Integer empId = rs.getInt(1); String name = rs.getString(2); String position = rs.getString(3); Double salary = rs.getDouble(4); 

3. Recuperar metadatos del ResultSet

En esta sección, veremos cómo recuperar información sobre las propiedades y tipos de columna en un ResultSet .

Primero, usemos el método getMetaData () en nuestro ResultSet para obtener el ResultSetMetaData :

ResultSetMetaData metaData = rs.getMetaData();

A continuación, obtengamos el número de columnas que hay en nuestro ResultSet :

Integer columnCount = metaData.getColumnCount();

Además, podemos usar cualquiera de los métodos siguientes en nuestro objeto de metadatos para recuperar las propiedades de cada columna:

  • getColumnName (int columnNumber) - para obtener el nombre de la columna
  • getColumnLabel (int columnNumber) : para acceder a la etiqueta de la columna, que se especifica después de AS en la consulta SQL
  • getTableName (int columnNumber) - para obtener el nombre de la tabla a la que pertenece esta columna
  • getColumnClassName (int columnNumber) - para adquirir el tipo de datos Java de la columna
  • getColumnTypeName (int columnNumber) - para obtener el tipo de datos de la columna en la base de datos
  • getColumnType (int columnNumber) - para obtener el tipo de datos SQL de la columna
  • isAutoIncrement (int columnNumber) : indica si la columna es de incremento automático
  • isCaseSensitive (int columnNumber) : especifica si el caso de la columna es importante
  • isSearchable (int columnNumber) : sugiere si podemos usar la columna en la cláusula where de la consulta SQL
  • isCurrency (int columnNumber) : indica si la columna contiene un valor en efectivo
  • isNullable (int columnNumber) : devuelve cero si la columna no puede ser nula, uno si la columna puede contener un valor nulo y dos si se desconoce la nulabilidad de la columna
  • isSigned (int columnNumber) : devuelve verdadero si los valores de la columna están firmados; de lo contrario, devuelve falso

Iteremos a través de las columnas para obtener sus propiedades:

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); }

4. Navegación por ResultSet

Cuando obtenemos un ResultSet , la posición del cursor está antes de la primera fila. Además, de forma predeterminada, el ResultSet se mueve solo en la dirección de avance. Pero podemos usar un ResultSet desplazable para otras opciones de navegación.

En esta sección, discutiremos las distintas opciones de navegación.

4.1. ResultSet Tipos

El tipo ResultSet indica cómo nos dirigiremos a través del conjunto de datos:

  • TYPE_FORWARD_ONLY: la opción predeterminada, en la que el cursor se mueve de principio a fin
  • TYPE_SCROLL_INSENSITIVE: nuestro cursor puede moverse a través del conjunto de datos en ambas direcciones hacia adelante y hacia atrás; si hay cambios en los datos subyacentes mientras se mueve a través del conjunto de datos, se ignoran; el conjunto de datos contiene los datos desde el momento en que la consulta de la base de datos devuelve el resultado
  • TYPE_SCROLL_SENSITIVE: similar al tipo insensible al desplazamiento, sin embargo, para este tipo, el conjunto de datos refleja inmediatamente cualquier cambio en los datos subyacentes.

No todas las bases de datos admiten todos los tipos de ResultSet . Entonces, verifiquemos si el tipo es compatible usando supportsResultSetType en nuestro objeto DatabaseMetaData :

DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);

4.2. ResultSet desplazable

Para obtener un ResultSet desplazable , necesitamos pasar algunos parámetros adicionales mientras preparamos la Declaración .

Por ejemplo, obtendríamos un ResultSet desplazable utilizando TYPE_SCROLL_INSENSITIVE o TYPE_SCROLL_SENSITIVE como tipo de ResultSet :

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery(); 

4.3. Opciones de navegación

Podemos usar cualquiera de las siguientes opciones en un ResultSet desplazable :

  • next () - pasa a la siguiente fila desde la posición actual
  • anterior () : pasa a la fila anterior
  • first (): navega a la primera fila del ResultSet
  • last () - salta a la última fila
  • beforeFirst () - se mueve al inicio; llamar a next () en nuestro ResultSet después de llamar a este método devuelve la primera fila de nuestro ResultSet
  • afterLast () - salta hasta el final; llamar a previous () en nuestro ResultSet después de ejecutar este método devuelve la última fila de nuestro ResultSet
  • relativo (int numOfRows) - avanza o retrocede desde la posición actual por numOfRows
  • absolute (int rowNumber) - salta al rowNumber especificado

Veamos algunos ejemplos:

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 Row Count

El uso de Let getRow () para obtener el número de la fila actual de nuestro conjunto de resultados .

Primero, navegaremos hasta la última fila del ResultSet y luego usaremos getRow () para obtener el número de registros:

rs.last(); int rowCount = rs.getRow();

5. Actualización de datos en un ResultSet

De forma predeterminada, ResultSet es de solo lectura. Sin embargo, podemos usar un ResultSet actualizable para insertar, actualizar y eliminar las filas.

5.1. Concurrencia de ResultSet

El modo de concurrencia indica si nuestro ResultSet puede actualizar los datos.

La opción CONCUR_READ_ONLY es la predeterminada y debe usarse si no necesitamos actualizar los datos usando nuestro ResultSet .

Sin embargo, si necesitamos actualizar los datos en nuestro ResultSet , entonces se debe usar la opción CONCUR_UPDATABLE .

No todas las bases de datos admiten todos los modos de concurrencia para todos los tipos de ResultSet . Por lo tanto, debemos verificar si nuestro tipo y modo de simultaneidad deseados son compatibles con el método supportsResultSetConcurrency () :

DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetConcurrency( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); 

5.2. La obtención de una actualizable conjunto de resultados

Para obtener un ResultSet actualizable , necesitamos pasar un parámetro adicional cuando preparamos la Declaración . Para eso, usemos CONCUR_UPDATABLE como tercer parámetro mientras creamos una declaración:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery();

5.3. Actualizar una fila

En esta sección, actualizaremos una fila usando el ResultSet actualizable creado en la sección anterior.

Podemos actualizar datos en una fila llamando a los métodos updateX () , pasando los nombres y valores de las columnas para actualizar. Podemos usar cualquier tipo de datos admitido en lugar de X en el método updateX () .

Actualicemos la columna "salario" , que es de tipo double :

rs.updateDouble("salary", 1100.0);

Tenga en cuenta que esto solo actualiza los datos en el ResultSet , pero las modificaciones aún no se guardan en la base de datos.

Finalmente, llamemos a updateRow () para guardar las actualizaciones en la base de datos :

rs.updateRow(); 

En lugar de los nombres de las columnas, podemos pasar el índice de la columna a los métodos updateX () . Esto es similar a usar el índice de columna para obtener los valores usando métodos getX () . Pasar el nombre de la columna o el índice a los métodos updateX () produce el mismo resultado:

rs.updateDouble(4, 1100.0); rs.updateRow(); 

5.4. Insertar una fila

Ahora, insertemos una nueva fila usando nuestro ResultSet actualizable .

Primero, usaremos moveToInsertRow () para mover el cursor para insertar una nueva fila:

rs.moveToInsertRow();

A continuación, debemos llamar a los métodos updateX () para agregar la información a la fila. Necesitamos proporcionar datos a todas las columnas de la tabla de la base de datos. Si no proporcionamos datos a cada columna, se usa el valor de columna predeterminado:

rs.updateString("name", "Venkat"); rs.updateString("position", "DBA"); rs.updateDouble("salary", 925.0);

Then, let's call insertRow() to insert a new row into the database:

rs.insertRow();

Finally, let's use moveToCurrentRow(). This will take the cursor position back to the row we were at before we started inserting a new row using the moveToInsertRow() method:

rs.moveToCurrentRow();

5.5. Deleting a Row

In this section, we'll delete a row using our updatable ResultSet.

First, we'll navigate to the row we want to delete. Then, we'll call the deleteRow() method to delete the current row:

rs.absolute(2); rs.deleteRow();

6. Holdability

The holdability determines if our ResultSet will be open or closed at the end of a database transaction.

6.1. Holdability Types

Use CLOSE_CURSORS_AT_COMMIT if the ResultSet is not required after the transaction is committed.

Use HOLD_CURSORS_OVER_COMMIT to create a holdable ResultSet. A holdable ResultSet is not closed even after the database transaction is committed.

Not all databases support all the holdability types.

So, let's check if the holdability type is supported using supportsResultSetHoldability() on our DatabaseMetaData object. Then, we'll get the default holdability of the database using 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. Holdable ResultSet

To create a holdable ResultSet, we need to specify the holdability type as the last parameter while creating a Statement. This parameter is specified after the concurrency mode.

Note that if we're using Microsoft SQL Server (MSSQL), we have to set holdability on the database connection, rather than on the ResultSet:

dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);

Let's see this in action. First, let's create a Statement, setting the holdability to HOLD_CURSORS_OVER_COMMIT:

Statement pstmt = dbConnection.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT)

Now, let's update a row while retrieving the data. This is similar to the update example we discussed earlier, except that we'll continue to iterate through the ResultSet after committing the update transaction to the database. This works fine on both MySQL and MSSQL databases:

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(); 

It's worth noting that MySQL supports only HOLD_CURSORS_OVER_COMMIT. So, even if we use CLOSE_CURSORS_AT_COMMIT, it will be ignored.

The MSSQL database supports CLOSE_CURSORS_AT_COMMIT. This means that the ResultSet will be closed when we commit the transaction. As a result, an attempt to access the ResultSet after committing the transaction results in a ‘Cursor is not open error’. Therefore, we can’t retrieve further records from the ResultSet.

7. Fetch Size

Typically, when loading data into a ResultSet, the database drivers decide on the number of rows to fetch from the database. On a MySQL database, for example, the ResultSet normally loads all the records into memory at once.

Sometimes, however, we may need to deal with a large number of records that won't fit into our JVM memory. In this case, we can use the fetch size property either on our Statement or ResultSet objects to limit the number of records initially returned.

Whenever additional results are required, ResultSet fetches another batch of records from the database. Using the fetch size property, we can provide a suggestion to the database driver on the number of rows to fetch per database trip. The fetch size we specify will be applied to the subsequent database trips.

If we don't specify the fetch size for our ResultSet, then the fetch size of the Statement is used. If we don't specify fetch size for either the Statement or the ResultSet, then the database default is used.

7.1. Using Fetch Size on Statement

Now, let's see the fetch size on Statement in action. We'll set the fetch size of the Statement to 10 records. If our query returns 100 records, then there will be 10 database round trips, loading 10 records each time:

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. Using Fetch Size on ResultSet

Now, let's change the fetch size in our previous example using the ResultSet.

First, we'll use the fetch size on our Statement. This allows our ResultSet to initially load 10 records after executing the query.

Then, we'll modify the fetch size on the ResultSet. This will override the fetch size we earlier specified on our Statement. So, all the subsequent trips will load 20 records until all the records are loaded.

As a result, there will be only 6 database trips to load all the records:

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 }

Finally, we'll see how to modify the fetch size of the ResultSet while iterating the results.

Similar to the previous example, we'll first set the fetch size to 10 on our Statement. So, our first 3 database trips will load 10 records per each trip.

Y luego, modificaremos el tamaño de recuperación en nuestro ResultSet a 20 mientras leemos el registro 30. Entonces, los próximos 4 viajes cargarán 20 registros por cada viaje.

Por lo tanto, necesitaremos 7 viajes a la base de datos para cargar los 100 registros:

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. Conclusión

En este artículo, vimos cómo usar la API ResultSet para recuperar y actualizar datos de una base de datos. Varias de las funciones avanzadas que discutimos dependen de la base de datos que estamos usando. Por lo tanto, debemos verificar el soporte de esas funciones antes de usarlas.

Como siempre, el código está disponible en GitHub.