Vamos a lanzar una query SQL en Java. La query que usaré será la siguiente:
SELECT * FROM alumnos
Si la tiro en mi base de datos, obtengo una tabla con los siguientes datos:
id | nombre | apellido | fecha_nac
---+--------+----------+-----------
8 | Ana | Claro | 1998-10-05
7 | Pepito | Perez | 1999-01-31
9 | Luis | Martinez | 1999-04-14
Cómo fabricar la query SQL
Cuando tengas una conexión abierta con tu base de datos, puedes usar el método Connection.createStatement() para empezar a crear tu query. Este método devuelve un objeto de tipo java.sql.Statement. Además, este objeto también se puede cerrar cuando hayamos terminado de trabajar con él, así que también es buena idea envolverlo en un try-with-resources.
// Tenemos un objeto conn con la conexión.
try (Statement stmt = conn.createStatement()) {
// Hacer algo con el statement.
} catch (SQLException e) {
// Haz algo con el error que se produce durante este statement.
e.printStackTrace();
}
Ese Statement tiene varios métodos para enviar sentencias SQL. En este caso, como quiero tirar una query que me recupere información, usaré un método llamado executeQuery. Este método tiene la siguiente forma, aceptando el string con la query SQL a tirar, haciendo la consulta, y devolviendo un ResultSet con los resultados.
ResultSet executeQuery(String sql) throws SQLException
El ResultSet es el objeto que permite navegar por los resultados en forma de cursor. Podremos navegar por cada fila, y para cada fila podremos revisar cada columna de interés. El ResultSet también se puede cerrar con una llamada a close() para liberar recursos y dejar de tener la consulta en memoria. Y sí, también lo puedes meter en un try-with-resources.
En definitiva, de la conexión sacamos el statement, y del statement el result set. Aquí está un ejemplo completo donde ya se limpia el try-with-resources para que sólo haya uno:
try (Connection conn = DriverManager.getConnection(url, props);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM alumnos")) {
// Hacer algo con los datos.
} catch (SQLException e) {
// Haz algo con el error que se produce durante este statement.
e.printStackTrace();
}
Cómo recorrer un ResultSet
El ResultSet representa una tabla como la que obtendrías al lanzar una consulta SQL desde cualquier otro programa.
Para empezar, deberías cuestionarte si tu ResultSet tiene resultados o si está vacío. El método next() te permite saltar a la siguiente fila de una consulta, como si fuese un iterador. Este método devuelve un boolean. Y cuando next() no puede pasar a la siguiente fila porque ya no hay más filas, entonces devuelve false. Si lo ha podido hacer bien, entonces devuelve true.
El secreto es que cuando obteienes un nuevo ResultSet, inicialmente estás antes de la primera fila, así que siempre vas a tener que llamar a next() al menos una vez para pasar a la primera. Ahí puedes empezar a usar ya el retorno de este método para saber si tienes resultados o no.
Ya te contaré en otra lección que también puedes usar un result set reversible, que se corre tanto hacia adelante como hacia atrás, y que te permite navegar a resultados anteriores.
En mi caso, voy a usar while() para iterar sobre cada resultado hasta que se haya quedado sin resultados:
try (Connection conn = DriverManager.getConnection(url, props);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM alumnos")) {
while (rs.next()) {
// Trabajar con este resultado
}
} catch (SQLException e) {
// Haz algo con el error que se produce durante este statement.
e.printStackTrace();
}
Finalmente, una vez que estemos ubicados en una fila, podemos usar los distintos getters de ResultSet para obtener una columna concreta. Existen muchísimos métodos, cada uno adaptado a un tipo de datos. Tendrás que usar el del tipo que quieres recuperar:
getString()para obtener un string.getInt()para obtener un valor numérico entero.getBoolean()para obtener un booleano.getDate()para obtener una fecha usando la clase tradicional de JavaDate(este no es el que querrás usar hoy en día, abajo te explico por qué).
Para cada uno de estos getters, hay dos formas de invocarlo como mínimo:
- Le puedes pasar el string con el nombre de la columna si lo sabes. Esta es la forma más verbosa y sencilla porque así puedes ver qué columna estás pidiendo. Por ejemplo,
getString("nombre")ogetBoolean("ADMIN"). - O le puedes pasar un número con el índice de columna. Por ejemplo,
getString(1)para obtener el string que hay en la primera columna,getInt(2)para recuperar el número que hay en la segunda columna... Sí, los índices empiezan a contar en 1.
Algunos drivers pueden tener además soporte para más tipos especiales. Por ejemplo, se ha vuelto muy frecuente que los drivers de base de datos permitan recuperar directamente instancias de LocalDate, LocalTime y otras clases modernas para trabajar con fechas en Java. La forma de hacer esto es mediante el método getObject(), que además de aceptar el string o el índice de columna, también acepta la referencia a la clase que quieres que te devuelva. Por ejemplo, si quieres obtener una fecha como LocalDate, con PostgreSQL podrías hacerlo como:
import java.time.LocalDate;
//
// ...
//
LocalDate fechaNac = rs.getObject("fecha_nac", LocalDate.class);
Vamos a incorporar estos getters al código que ponía antes para fabricar el ejemplo completo y sacar el nombre, apellidos y fecha de nacimiento de cada persona de mi tabla:
try (Connection conn = DriverManager.getConnection(url, props);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM alumnos")) {
while (rs.next()) {
int id = rs.getInt("id");
String nombre = rs.getString("nombre");
String apellido = rs.getString("apellido");
Date fechaNac = rs.getDate("fecha_nac");
String linea = nombre + " " + apellido + "(" + id + ", " + fechaNac + ")";
System.out.println("Resultado: " + linea);
}
} catch (SQLException e) {
// Haz algo con el error que se produce durante este statement.
e.printStackTrace();
}