9.5 Consultas (Query)
1. Navegación de un ResultSet¶
Como ya se ha visto, en un objeto ResultSet se encuentran los resultados de la ejecución de una sentencia SQL. Por lo tanto, un objeto ResultSet contiene las filas que satisfacen las condiciones de una sentencia SQL, y ofrece métodos de navegación por los registros como next() que desplaza el cursos al siguiente registro del ResultSet.
Además de este método de desplazamiento básico, existen otros de desplazamiento libre que podremos utilizar siempre y cuando el ResultSet sea de tipo ResultSet.TYPE_SCROLL_INSENSITIVE o ResultSet.TYPE_SCROLL_SENSITIVE como se ha dicho antes.
Algunos de estos métodos son:
-
void beforeFirst(): mueve el cursor antes de la primera fila. -
boolean first(): mueve el cursor a la primera fila. -
boolean next(): mueve el cursor a la siguiente fila. Permitido en todos los tipos de ResultSet. -
boolean previous(): mueve el cursor a la fila anterior. -
boolean last(): mueve el cursor a la última fila. -
void afterLast(): mover el cursor después de la última fila. -
boolean absolute(int row): posiciona el cursor en el número de registro indicado. Hay que tener en cuenta que el primer registro es el 1, no el cero.
Ejemplo absolute(n)
absolute(7) desplazará el cursor al séptimo registro. Si valor es negativo se posiciona en el número de registro indicado pero empezando a contar desde el final (el último es el -1). Por ejemplo si tiene 10 registros y llamamos absolute(-2) se desplazará al registro número 9.
boolean relative(int registros): desplaza el cursor un número relativo de registros, que puede ser positivo o negativo.
Ejemplo relative(n)
Si el cursor está en el registro 5 y llamamos a relative(10) se desplazará al registro número 15. Si luego llamamos a relative(-4) se desplazará al registro 11.
Los métodos que devuelven un tipo boolean devolverán true si ha sido posible mover el cursor a un registro válido, y false en caso contrario, por ejemplo si no tiene ningún registro o hemos saltado a un número de registro que no existe.
Todos estos métodos pueden producir una excepción de tipo SQLException.
También existen otros métodos relacionados con la posición del cursor.
int getRow(): devuelve el número de registro actual. Cero si no hay registro actual.boolean isBeforeFirst(): devuelve true si el cursor está antes del primer registro.boolean isFirst(): devuelve true si el cursor está en el primer registro.boolean isLast(): devuelve true si el cursor está en el último registro.boolean isAfterLast(): devuelve true si el cursor está después del último registro.
2. Obteniendo datos del ResultSet¶
Los métodos getXXX() ofrecen los medios para recuperar los valores de las columnas (campos) de la fila (registro) actual del ResultSet. No es necesario que las columnas sean obtenidas utilizando un orden determinado.
Para designar una columna podemos utilizar su nombre o bien su número (empezando por 1).
Por ejemplo si la segunda columna de un objeto ResultSet se llama título y almacena datos de tipo String, se podrá recuperar su valor de las dos formas siguientes:
Es importante tener en cuenta que las columnas se numeran de izquierda a derecha y que la primera es la número 1, no la cero.
También que las columnas no son case sensitive, es decir, no distinguen entre mayúsculas y minúsculas.
A tener en cuenta
La información referente a las columnas de un ResultSet se puede obtener llamando al método getMetaData() que devolverá un objeto ResultSetMetaData que contendrá el número, tipo y propiedades de las columnas del ResultSet. EJEMPLO:
Si conocemos el nombre de una columna, pero no su índice, el método findColumn() puede ser utilizado para obtener el número de columna, pasándole como argumento un objeto String que sea el nombre de la columna correspondiente, este método nos devolverá un entero que será el índice correspondiente a la columna.
3. Tipos de datos y conversiones¶
Cuando se lanza un método getXXX() determinado sobre un objeto ResultSet para obtener el valor de un campo del registro actual, el driver JDBC convierte el dato que se quiere recuperar al tipo Java especificado y entonces devuelve un valor Java adecuado. Por ejemplo si utilizamos el método getString() y el tipo del dato en la base de datos es VARCHAR, el driver JDBC convertirá el dato VARCHAR de tipo SQL a un objeto String de Java.
Algo parecido sucede con otros tipos de datos SQL como por ejemplo DATE. Podremos acceder a él tanto con getDate() como con getString(). La diferencia es que el primero devolverá un objeto Java de tipo Date y el segundo devolverá un String.
Siempre que sea posible el driver JDBC convertirá el tipo de dato almacenado en la base de datos al tipo solicitado por el método getXXX(), pero hay conversiones que no se pueden realizar y lanzarán una excepción, como por ejemplo si intentamos hacer un getInt() sobre un campo que no contiene un valor numérico.
4. Sentencias que no devuelven datos¶
Las ejecutamos con el método executeUpdate. Serán todas las sentencias SQL excepto el SELECT, que es la de consulta. Es decir, nos servirá para las siguientes sentencias:
- Sentencias que cambian las estructuras internas de la BD donde se guardan los datos (instrucciones conocidas con las siglas DDL, del inglés Data Definition Language), como por ejemplo
CREATE TABLE,CREATE VIEW,ALTER TABLE,DROP TABLE, …, - Sentencias para otorgar permisos a los usuarios existentes o crear otros nuevos (subgrupo de instrucciones conocidas como DCL o Data Control Language), como por ejemplo
GRANT. - Y también las sentencias para modificar los datos guardados utilizando las instrucciones
INSERT,UPDATEyDELETE.
Aunque se trata de sentencias muy dispares, desde el punto de vista de la comunicación con el SGBD se comportan de manera muy similar, siguiendo el siguiente patrón:
- Instanciación del
Statementa partir de una conexión activa. - Ejecución de una sentencia SQL pasada por parámetro al método
executeUpdate. - Cierre del objeto
Statementinstanciado.
Miremos este ejemplo, en el que vamos a crear una tabla muy sencilla en la Base de Datos MySql/network.
Aquí tienes la clase DatabaseConnection
5. Sentencias que devuelven datos¶
Las ejecutamos con el método executeQuery. Servirá para la sentencia SELECT (sentencia de consulta). Los datos que nos devuelva esta sentencia las tendremos que guardar en un objeto de la clase java.sql.ResultSet, es decir conjunto de resultado. Por lo tanto, la ejecución de las consultas tendrá una forma similar a la siguiente:
- El objeto
ResultSetcontiene el resultado de la consulta organizado por filas, por lo que en cada momento se puede consultar una fila. - Para ir visitando todas las filas de una a una, iremos llamando el método
next()del objetoResultSet, ya que cada vez que se ejecutenextavanzará a la siguiente fila. - Inmediatamente después de una ejecución, el
ResultSetse encuentra posicionado justo antes de la primera fila, por lo tanto para acceder a la primera fila será necesario ejecutarnextuna vez. - Cuando las filas se acaban, el método
nextdevolverá falso.
Desde cada fila se podrá acceder al valor de sus columnas con ayuda de varios métodos getXXX disponibles según el tipo de datos a devolver y pasando por parámetro el número de columna que deseamos obtener. El nombre de los métodos comienza por get seguido del nombre del tipo de datos. Así, si queremos recuperar la segunda columna, sabiendo que es un dato de tipo String habrá que ejecutar:
Las columnas se empiezan a contar a partir del valor 1 (no cero). La mayor parte de los SGDB soportan la posibilidad de pasar por parámetro el nombre de la columna, pero no todos, así que normalmente se opta por el parámetro numérico.
Por ejemplo MySql sí que deja acceder por nombre, por tanto, suponiendo que el campo 1 se llama id, también se puede hacer:
En este ejemplo accedemos a la tabla usuarios y mostramos todos sus registros
6. Asegurar la liberación de recursos¶
Las instancias de Connection y las de Statement guardan, en memoria, mucha información relacionada con las ejecuciones realizadas. Además, mientras continúan activas mantienen en el SGBD una sesión abierta, que supondrá un conjunto importante de recursos abiertos, destinados a servir de forma eficiente las peticiones de los clientes. Es importante cerrar estos objetos para liberar recursos tanto del cliente como del servidor.
Si en un mismo método debemos cerrar un objeto Statement y el Connection a partir del cual la hemos creado, se deberá cerrar primero el Statement y después el Connection. Si lo hacemos al revés, cuando intentamos cerrar el Statement nos saltará una excepción de tipo SQLException, ya que el cierre de la conexión le habría dejado inaccesible.
Además de respetar el orden, asegurar la liberación de los recursos situando las operaciones de cierre dentro de un bloque finally. De este modo, aunque se produzcan errores, no se dejarán de ejecutar las instrucciones de cierre.
Hay que tener en cuenta todavía un detalle más cuando sea necesario realizar el cierre de varios objetos a la vez. En este caso, aunque las situamos una tras otra, todas las instrucciones de cierre dentro del bloque finally, no sería suficiente garantía para asegurar la ejecución de todos los cierres, ya que, si mientras se produce el cierre de uno de los objetos se lanza una excepción, los objetos invocados en una posición posterior a la del que se ha producido el error no se cerrarán.
La solución de este problema pasa por evitar el lanzamiento de cualquier excepción durante el proceso de cierre. Una posible forma es encapsular cada cierre entre sentencias try-catch dentro del finally.