ResultSet
Navegación y mapeo de filas a objetos
Navegación básica
ResultSet es un cursor que empieza antes de la primera fila.
next() avanza una posición y devuelve true si la nueva fila existe, false si se ha llegado al final. El patrón while (rs.next())
recorre todas las filas.
import java.sql.*;
// ResultSet: cursor que apunta a las filas del resultado
// Por defecto: TYPE_FORWARD_ONLY — solo avanza, una fila a la vez
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id, nombre, email FROM clientes");
ResultSet rs = ps.executeQuery()) {
// rs.next() avanza el cursor y devuelve false cuando no hay más filas
while (rs.next()) {
// Acceso por nombre de columna (más legible, recomendado)
long id = rs.getLong("id");
String nombre = rs.getString("nombre");
String email = rs.getString("email");
// Acceso por índice (base 1, más rápido pero menos mantenible)
long id2 = rs.getLong(1);
String nombre2 = rs.getString(2);
System.out.println(id + ": " + nombre + " <" + email + ">");
}
} Tipos y NULL
Cada tipo SQL tiene un método getXxx correspondiente. Para tipos numéricos primitivos (getInt, getLong...), si el valor es SQL NULL el método devuelve 0 — hay que llamar a wasNull() inmediatamente después para distinguir el 0 real del NULL.
// Métodos de lectura del ResultSet según el tipo SQL:
ResultSet rs = ps.executeQuery();
while (rs.next()) {
long id = rs.getLong("id"); // BIGINT
int cantidad = rs.getInt("cantidad"); // INT
String nombre = rs.getString("nombre"); // VARCHAR, TEXT, CHAR
double precio = rs.getDouble("precio"); // FLOAT, DOUBLE (usar BigDecimal para dinero)
BigDecimal total = rs.getBigDecimal("total"); // DECIMAL, NUMERIC
boolean activo = rs.getBoolean("activo"); // BOOLEAN, BIT
Date fecha = rs.getDate("fecha"); // DATE → java.sql.Date
Timestamp ts = rs.getTimestamp("creado"); // TIMESTAMP → java.sql.Timestamp
byte[] datos = rs.getBytes("blob_col"); // BLOB, BINARY
// Detectar NULL: getXxx devuelve 0/false/null para tipos primitivos y referencia
// Para primitivos, verificar con wasNull() después de la llamada
int valorNulable = rs.getInt("opcional");
if (rs.wasNull()) {
System.out.println("El campo era NULL");
}
} Row mapping a objetos
El patrón habitual es encapsular la conversión de ResultSet a objeto de dominio en una clase o método
separado (row mapper). Esto es exactamente lo que Spring JDBC hace con RowMapper<T>.
// Mapeo de fila a objeto de dominio (row mapping)
public class ClienteMapper {
public static Cliente mapear(ResultSet rs) throws SQLException {
Cliente c = new Cliente();
c.setId(rs.getLong("id"));
c.setNombre(rs.getString("nombre"));
c.setEmail(rs.getString("email"));
c.setActivo(rs.getBoolean("activo"));
// Convertir java.sql.Date a java.time.LocalDate
Date fecha = rs.getDate("fecha_alta");
if (fecha != null) c.setFechaAlta(fecha.toLocalDate());
return c;
}
}
// Uso en el repositorio:
public List<Cliente> buscarActivos() throws SQLException {
String sql = "SELECT id, nombre, email, activo, fecha_alta FROM clientes WHERE activo = TRUE";
List<Cliente> resultado = new ArrayList<>();
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
resultado.add(ClienteMapper.mapear(rs));
}
}
return resultado;
} Metadatos del resultado
// Metadatos del ResultSet: estructura de las columnas
try (ResultSet rs = ps.executeQuery()) {
ResultSetMetaData meta = rs.getMetaData();
int numColumnas = meta.getColumnCount();
for (int i = 1; i <= numColumnas; i++) {
System.out.println(
meta.getColumnName(i) + " (" + meta.getColumnTypeName(i) + ")"
);
}
// Útil para herramientas genéricas que no conocen la estructura de antemano
}