Sorprendentemente, un ResultSet no solamente se puede usar para consultar información en una query, sino que también podemos usarlos para modificar o insertar información. A partir de una query, JDBC te permite modificar directamente los datos de una fila según la tienes seleccionada en un ResultSet. También puedes insertar filas nuevas desde una query que ya tienes en curso.
Definir un Statement de lectura y escritura
Cuando utilices el método createStatement() para fabricar un Statement con el que tirar queries, puedes pasarle adicionalmente algunos parámetros extra. Esto es porque este método está sobrecargado. Típicamente cuando aprendes JDBC, lo primero que ves es la llamada a createStatement() sin parámetros, pero hay varias formas de invocar este método:
Statement createStatement() throws SQLExceptionStatement createStatement(int resultSetType, int resultSetConcurrency) throws SQLExceptionStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
El parámetro resultSetType se usa para especificar si quieres que el ResultSet pueda avanzar hacia adelante y hacia atrás, o solamente hacia adelante, como te contaba en la lección correspondiente.
Sin embargo, el segundo parámetro, resultSetConcurrency, nos permite establecer si queremos que el ResultSet se pueda usar solo para leer información, o si queremos permitir que se pueda volver a modificar la base de datos usando el mismo ResultSet. De este modo, podríamos modificar el valor de una fila que hayamos abierto con ResultSet, o incluso agregar filas nuevas. Este parámetro puede adoptar dos posibles valores:
ResultSet.CONCUR_READ_ONLY: este es el modo por defecto, que solo permite usar un ResultSet para leer información.ResultSet.CONCUR_UPDATABLE: este es el que te permite modificar información con un ResultSet.
Cambiar el valor de una columna desde un ResultSet
Supongamos que tenemos una consulta SQL como la siguiente, que nos devuelve las citas que tiene una clínica, incluyendo información sobre si han sido confirmadas o no.
SELECT id, paciente_id, fecha_cita, confirmado FROM citas;
Esto se puede traducir por un código JDBC como el siguiente:
try (Statement stmt = conn.createStatement()) {
var sql = "SELECT id, paciente_id, fecha_cita, confirmado FROM citas";
try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
imprimirCita(rs);
}
}
}
En principio, esta es una operación de solo lectura que recorre las filas para imprimir sus datos:
void imprimirCita(ResultSet rs) {
int id = rs.getInt("id");
int paciente = rs.getInt("paciente_id");
LocalDate fechaCita = rs.getObject("fecha_cita", LocalDate.class);
boolean confirmado = rs.getBoolean("confirmado");
// Utilizo printf() para que queden verticalmente ordenaditos.
System.out.printf("%3d %3d %15s %s\n", id, paciente, fechaCita.toString(), confirmado ? "SI" : "NO");
}
Supongamos que mi programa tiene la capacidad de ir confirmando las citas sobre la marcha, cambiando el valor de la columna confirmado a true para aquellas filas que todavía no estuviesen confirmadas. Para hacer ese cambio, primero tendríamos que modificar la llamada a createStatement:
--- try (Statement stmt = conn.createStatement()) {
+++ try (Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE))
Hecho eso, cuando tengamos el cursor del ResultSet posicionado sobre una fila, podremos usar alguno de los métodos update para actualizar el valor de alguna de las columnas de esa fila. Los métodos update se parecen a los métodos set de un PreparedStatement, o a los métodos get del mismo ResultSet. Existe una colección de métodos, como por ejemplo:
updateString()para ponerle un string.updateInt()para ponerlre un valor numérico entero.updateBoolean()para ponerle un booleano.
En todos los casos, le especificas como primer parámetro el nombre de la columna, o bien su posición empezando a contar en 1, y como segundo parámetro, generalmente le pasas el valor.
Cuando hayamos terminado de hacer las modificaciones, es necesario llamar al método ResultSet.updateRow() para aplicar los cambios.
De modo que en este caso, si quisiéramos cambiar el valor de la columna confirmado a true, lo podríamos hacer con el siguiente código:
try (Statement stmt = conn.createStatement()) {
var sql = "SELECT id, paciente_id, fecha_cita, confirmado FROM citas";
try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
imprimirCita(rs);
rs.updateBoolean("confirmado", true);
rs.updateRow();
}
}
}
Por cierto, si tu ResultSet fuese de tipo SENSITIVE, si ahora utilizases los métodos de navegación de un ResultSet para moverte por las filas, verías los cambios al instante, debido a que este tipo de ResultSet está pensado para ver reflejados los cambios en el momento que se aplican a medida que navegas por los resultados de la consulta.
Borrar resultados
Este sistema lo podemos usar también para borrar filas. Supongamos que tenemos el cursor de un ResultSet posicionado sobre un record que queremos borrar. Podemos hacerlo llamando al método deleteRow(). Por ejemplo, el siguiente código borraría cada record de la tabla de citas que se corresponda con el mes de marzo:
try (Statement stmt = conn.createStatement()) {
var sql = "SELECT id, paciente_id, fecha_cita, confirmado FROM citas";
try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
LocalDate fecha = rs.getdate("fecha_cita").toLocalDate();
if (fecha.getMonth() == Month.MARCH) {
rs.deleteRow();
}
}
}
}
Una puntualización sobre este método: no siempre es útil. Viendo este ejemplo, es evidente que para borrar datos de este modo, bien podría haber hecho una única sentencia SQL:
DELETE FROM citas WHERE DATE_PART('month', fecha_cita) = 3
Creo que se entiende lo que quiero decir: no uses esto donde deberías usar un DELETE.
Insertar filas
Por último, también puedes insertar filas desde un ResultSet. Para ello, dentro de un ResultSet configurado en modo escritura, puedes primero cambiar a una fila nueva utilizando el método ResultSet.moveToInsertRow(). Esto llevará el cursor del ResultSet a una de esas filas virtuales, que está capacitada para crear registros nuevos.
Una vez dentro, puedes usar otra vez los métodos update para hacer la inserción de un nuevo registro, aprovechando la query que ya tienes abierta. Termina llamando al método insertRow() para confirmar la inserción y guardar los datos en el sistema:
public void insertar(ResultSet rs) {
rs.moveToInsertRow();
rs.updateInt("paciente_id", 1);
rs.updateObject("fecha_cita", LocalDate.of(2025, 2, 16));
rs.updateBoolean("confirmado", true);
rs.insertRow();
}
De nuevo, ten en cuenta que esto no debería ser un sustituto de utilizar INSERT si te hace falta. El código será mucho más corto y por lo tanto mantenible, y tus intenciones serán más clara que si lo haces de este modo.